skip to content

Cupertino Components

CupertinoApp, CupertinoPageScaffold and CupertinoNavigationBar give an iOS look, and .adaptive constructors switch per platform. Interviewers ask when to go adaptive versus one brand look.

part ofFlutteroverview, primer and where to startread it →
on this pageshow

explore

questions

5

In Flutter, what do CupertinoApp, CupertinoPageScaffold and CupertinoNavigationBar give you compared with MaterialApp, Scaffold and AppBar?

level: juniorimportance: should knowfreq 45%

answer

  1. iOS look from Flutter's own widgets
  2. CupertinoThemeData instead of ThemeData
  3. CupertinoPageRoute for plain routes
  4. middle, leading, trailing, previousPageTitle
  5. Material widgets need a Material ancestor

basics

~20 s

They are Flutter's own iOS-style counterparts: CupertinoApp sets up navigation, a CupertinoThemeData and iOS page routes; CupertinoPageScaffold lays out a page under an optional navigation bar; CupertinoNavigationBar draws the iOS bar with leading, middle and trailing slots.

solid answer

~40 s

`CupertinoApp` is the iOS-styled app root: it wires a `Navigator`, localizations and a `CupertinoTheme` from `CupertinoThemeData`, and builds `CupertinoPageRoute`s for `home` and `routes`. `CupertinoPageScaffold` is much thinner than `Scaffold`: a `navigationBar`, a `child`, a `backgroundColor` and `resizeToAvoidBottomInset`, with no drawer, FAB or snack bars. `CupertinoNavigationBar` takes `leading`, `middle` and `trailing`, can show a back button labelled with `previousPageTitle`, and by default is translucent with a background blur. These are Flutter widgets painted by Flutter, not wrappers around UIKit, so they look the same on every platform you run them on. Many Material widgets, such as `ListTile` or `TextField`, need a `Material` ancestor or `MaterialLocalizations`, so under a bare `CupertinoApp` you use Cupertino equivalents like `CupertinoListTile` and `CupertinoTextField`.

code

dart · 36 lines
dart
import 'package:flutter/cupertino.dart';

void main() => runApp(const CellarApp());

class CellarApp extends StatelessWidget {
  const CellarApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const CupertinoApp(
      theme: CupertinoThemeData(primaryColor: CupertinoColors.systemRed),
      home: CellarPage(),
    );
  }
}

class CellarPage extends StatelessWidget {
  const CellarPage({super.key});

  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: CupertinoNavigationBar(
        middle: const Text('My Cellar'),
        trailing: CupertinoButton(
          padding: EdgeInsets.zero,
          onPressed: () {},
          child: const Icon(CupertinoIcons.add),
        ),
      ),
      child: const SafeArea(
        child: Center(child: Text('42 bottles')),
      ),
    );
  }
}

go deeper

for a junior

Recall the three widgets, that CupertinoApp takes a CupertinoThemeData, and the leading, middle and trailing slots of the navigation bar.

for a middle

Explain what CupertinoPageScaffold lacks compared with Scaffold, the translucent bar, and why Material widgets fail under a bare CupertinoApp.

for a senior

Discuss how you mix Cupertino and Material widgets safely, and which one owns the app root when a product needs both.

for a principal

Relate these widgets to the broader choice between an iOS-styled app and a single brand look, and what each costs in maintenance.

## Two widget libraries, one engine Flutter ships two design libraries on top of its core widgets: **Material** (`package:flutter/material.dart`) and **Cupertino** (`package:flutter/cupertino.dart`, also published as the standalone `cupertino_ui` package since Flutter 3.47). Cupertino implements Apple's Human Interface Guidelines look. Crucially, both are drawn by Flutter itself: a `CupertinoNavigationBar` is a Flutter widget that paints an iOS-style bar, not a `UINavigationBar`. That is why it renders identically on Android, and why platform behaviours like the back-swipe are reimplemented rather than inherited. ## CupertinoApp `CupertinoApp` plays the role of `MaterialApp` for an iOS-styled app: - It builds a `Navigator` and, for `home` and the `routes` table, creates **`CupertinoPageRoute`** pages with the iOS slide transition. - It takes a **`CupertinoThemeData`** through `theme`, not a Material `ThemeData`. The main fields are `primaryColor`, `primaryContrastingColor`, `barBackgroundColor`, `scaffoldBackgroundColor`, `textTheme` and `brightness`. - It has no `darkTheme` or `themeMode`. When `CupertinoThemeData.brightness` is null, Cupertino widgets follow the platform brightness, and system colors such as `CupertinoColors.systemBackground` are `CupertinoDynamicColor`s that resolve to their light or dark variant automatically. ## CupertinoPageScaffold Its constructor is short: `navigationBar`, `backgroundColor`, `resizeToAvoidBottomInset` (default `true`) and a required `child`. Compared with `Scaffold`: | Feature | `Scaffold` | `CupertinoPageScaffold` | |---|---|---| | Top bar | `appBar` (`PreferredSizeWidget`) | `navigationBar` (`ObstructingPreferredSizeWidget`) | | Drawer, FAB, bottom sheet | Yes | No | | Snack bars | Through `ScaffoldMessenger` | No | | Content under a translucent bar | Only with `extendBodyBehindAppBar` | Yes, by default | | Tabs | Put a `NavigationBar` in `bottomNavigationBar` | Use `CupertinoTabScaffold` | The last difference matters: the default Cupertino bar background is slightly translucent, so the scaffold lets the `child` draw behind it and reports the overlap through `MediaQuery` padding instead of pushing content down. ## CupertinoNavigationBar The bar has three slots and several iOS behaviours built in: - **`leading`**, **`middle`** and **`trailing`** widgets. `automaticallyImplyLeading` (default `true`) adds a back chevron when the route can pop, and **`previousPageTitle`** labels it with the previous page's title, as iOS does. - **`backgroundColor`** defaults to the theme's `barBackgroundColor`, which is translucent; `enableBackgroundFilterBlur` (default `true`) blurs what scrolls beneath. - **`transitionBetweenRoutes`** (default `true`) animates the bar's contents between pages with a hero-like transition. - A **`CupertinoNavigationBar.large`** constructor and the sliver version, `CupertinoSliverNavigationBar`, provide the iOS large title. Text in the bar does not scale with the system text size by default, matching native iOS behaviour. ## CupertinoButton `CupertinoButton` is the iOS text-style button: it fades to `pressedOpacity` (default `0.4`) while pressed instead of showing a ripple. `CupertinoButton.filled` and `CupertinoButton.tinted` give the filled and tinted styles, `sizeStyle` (default `CupertinoButtonSize.large`) picks small, medium or large metrics, and a null `onPressed` (with a null `onLongPress`) disables it. `minSize` is deprecated in favour of `minimumSize`. ## Choosing Cupertino widgets for an iOS-first app For an iOS-first wine-cellar app, the Cupertino set covers most screens: `CupertinoListSection` and `CupertinoListTile` for grouped settings-style lists, `CupertinoTextField` and `CupertinoSearchTextField` for input, `CupertinoSegmentedControl` or `CupertinoSlidingSegmentedControl` for filters, `CupertinoActionSheet` and `CupertinoAlertDialog` for choices, `CupertinoDatePicker` and `CupertinoPicker` for vintages and dates, and `CupertinoTabScaffold` with `CupertinoTabBar` for top-level tabs. Where the set has no counterpart, such as a data table or a chip, you either compose your own widget from core widgets or wrap a Material widget in a `Material` surface. Knowing these names is what interviewers look for at this level: they want to hear that the Cupertino library is a complete kit rather than a handful of look-alikes. ## Mixing Material widgets into a Cupertino app A bare `CupertinoApp` provides no `Material` widget and no `MaterialLocalizations`. Material widgets that depend on them, such as `ListTile`, `TextField` or `InkWell`, fail in debug mode with errors like *No Material widget found* or *No MaterialLocalizations found*. Either use Cupertino counterparts (`CupertinoListTile`, `CupertinoTextField`, `CupertinoListSection`) or wrap the subtree in a `Material` widget and add the Material localizations delegates. In the other direction, Cupertino widgets work fine under `MaterialApp`, because the Material `Theme` provides a `CupertinoTheme` derived from the Material theme, adjustable through `ThemeData.cupertinoOverrideTheme`.

  • In Flutter, why does a ListTile placed under a bare CupertinoApp throw in debug mode?
    `ListTile` asserts that a `Material` ancestor exists, because its ink effects paint on one, and several Material widgets also need `MaterialLocalizations`. `CupertinoApp` provides neither. Use `CupertinoListTile`, or wrap the subtree in `Material` and add the Material localizations delegates to `CupertinoApp`.
  • In Flutter, how do Cupertino widgets under a MaterialApp pick up the app's colors?
    The Material `Theme` widget provides a `CupertinoTheme` for descendants, derived from the Material `ThemeData` when no `CupertinoTheme` is above it. You can override the derived values with `ThemeData.cupertinoOverrideTheme`, or wrap a subtree in its own `CupertinoTheme`.

saying these in an interview costs you the question

  • Saying CupertinoNavigationBar wraps the native UINavigationBar
  • Passing a Material ThemeData to CupertinoApp's theme parameter
  • Expecting CupertinoPageScaffold to offer a drawer or floating action button
  • Using ListTile and TextField under a bare CupertinoApp without a Material ancestor
  • Expecting CupertinoApp to have darkTheme and themeMode like MaterialApp
open as a page

In Flutter, what do constructors like Switch.adaptive, Slider.adaptive and CircularProgressIndicator.adaptive do, and how do they decide which look to render?

level: middleimportance: should knowfreq 42%

basics

~10 s

They render the Cupertino look when Theme.of(context).platform is iOS or macOS and the Material widget elsewhere. The platform comes from ThemeData.platform, which defaults to defaultTargetPlatform, and some Material-only parameters are ignored on Apple platforms.

open as a page

In a Flutter CupertinoPageScaffold, why can the first row of content hide under the CupertinoNavigationBar, and how do you fix it?

level: middleimportance: should knowfreq 30%

basics

~20 s

The default Cupertino bar background is translucent, so the scaffold lets the child draw behind it and only reports the overlap as MediaQuery top padding. Content that ignores that padding, such as a Column or a ListView with explicit padding, starts under the bar.

open as a page

For an iOS-first Flutter wine-cellar app that must feel native on iPhone and also ship on Android, how do you choose between one brand look and platform-adaptive UI?

level: principalimportance: should knowfreq 35%

basics

~20 s

Decide by what users notice and what the team can maintain: keep brand surfaces shared, adapt the controls and navigation conventions iPhone users expect (switches, dialogs, back swipe, pickers), and avoid two full widget trees unless the product truly needs them.

open as a page

In Flutter 3.47, what is the cupertino_ui package, and how does it relate to package:flutter/cupertino.dart?

level: middleimportance: nice to knowfreq 22%

basics

~20 s

cupertino_ui is the standalone pub package of Flutter's Cupertino library. The in-SDK package:flutter/cupertino.dart has been frozen since Flutter 3.44; cupertino_ui 1.0 started from that frozen code and now evolves on its own release cycle, needing Flutter 3.47 or later.

open as a page