Cupertino (iOS)
CupertinoApp + Cupertino widgets: iOS-style UI in Flutter. When to mix with Material, and the platform-aware patterns.
Flutter — Cupertino widgets
EXAMPLE
import 'package:flutter/cupertino.dart';
// ===== A Cupertino app =====
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return CupertinoApp(
title: 'Cupertino',
theme: const CupertinoThemeData(brightness: Brightness.light),
home: const Home(),
);
}
}
// ===== Page with NavigationBar =====
class Home extends StatelessWidget {
const Home({super.key});
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: const CupertinoNavigationBar(
middle: Text('Home'),
trailing: Icon(CupertinoIcons.search),
),
child: SafeArea(
child: Column(
children: [
const SizedBox(height: 16),
CupertinoButton.filled(
child: const Text('Tap me'),
onPressed: () {},
),
const SizedBox(height: 12),
CupertinoTextField(placeholder: 'Search...'),
],
),
),
);
}
}
// ===== Tabs =====
class TabsApp extends StatelessWidget {
const TabsApp({super.key});
@override
Widget build(BuildContext context) {
return CupertinoTabScaffold(
tabBar: CupertinoTabBar(items: const [
BottomNavigationBarItem(icon: Icon(CupertinoIcons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(CupertinoIcons.settings), label: 'Settings'),
]),
tabBuilder: (ctx, i) => CupertinoTabView(
builder: (ctx) => i == 0 ? const Home() : const Settings(),
),
);
}
}
class Settings extends StatelessWidget {
const Settings({super.key});
@override
Widget build(BuildContext context) => const Center(child: Text('Settings'));
}
// ===== Common widgets =====
// CupertinoButton, CupertinoButton.filled
// CupertinoTextField
// CupertinoSwitch, CupertinoSlider
// CupertinoPicker (wheel picker)
// CupertinoDatePicker
// CupertinoAlertDialog, CupertinoActionSheet
// CupertinoActivityIndicator (spinner)
// CupertinoSegmentedControl
// CupertinoListSection, CupertinoListTile
// ===== Dialogs + action sheets =====
showCupertinoDialog(
context: context,
builder: (ctx) => CupertinoAlertDialog(
title: const Text('Delete?'),
content: const Text('This cannot be undone.'),
actions: [
CupertinoDialogAction(child: const Text('Cancel'), onPressed: () => Navigator.pop(ctx)),
CupertinoDialogAction(isDestructiveAction: true, child: const Text('Delete'), onPressed: () { /* ... */ Navigator.pop(ctx); }),
],
),
);
// ===== Platform-aware pattern =====
// Show Material on Android, Cupertino on iOS:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart' as material;
Widget platformApp() {
if (defaultTargetPlatform == TargetPlatform.iOS) {
return const MyApp(); // CupertinoApp
}
return material.MaterialApp(home: const Home());
}
// Or use packages like flutter_platform_widgets to abstract.
// ===== When Cupertino wins =====
// - iOS-first apps where you want native look
// - App store reviewers expecting iOS UI conventions
// - Apple Watch / iPad apps
// ===== When Material is fine =====
// - Cross-platform apps with consistent design language
// - You want one widget set across both platforms
// - Internal tools, prototypes, MVPs
// ===== Patterns to internalise =====
// - One design system per app: pick Material or Cupertino, do not mix
// - CupertinoIcons (not Icons) for iOS look
// - SafeArea + CupertinoPageScaffold at the root
// - flutter_platform_widgets if you genuinely need both per platform
// ===== Pitfalls =====
// - Mixing Cupertino + Material widgets randomly
// - Forgetting that Material vs Cupertino theme data are SEPARATE
// - Custom dark mode handling — CupertinoThemeData differs from ThemeData
// - showCupertinoDialog from a Material ancestor — works but visually inconsistent
Why it matters
CupertinoApp + Cupertino widgets give you iOS-flavoured UI without writing Swift. Pick Cupertino or Material at the app level, lean on CupertinoPageScaffold + SafeArea + CupertinoIcons. Mix only when there is a specific reason; design system consistency is the dial users feel.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
import 'package:flutter/cupertino.dart';
CupertinoApp(
home: CupertinoPageScaffold(
navigationBar: const CupertinoNavigationBar(middle: Text('iOS')),
child: const Center(child: Text('Hi')),
),
);
Try it Yourself »
Discussion
Loading…