Flutter CupertinoTabBar Localization: iOS Tab Navigation for Multilingual Apps
CupertinoTabBar is a Flutter widget that renders an iOS-style bottom tab bar for navigating between different sections of an app. In multilingual applications, CupertinoTabBar is essential for displaying translated tab labels that match iOS platform conventions, providing localized icon-label combinations for each tab destination, supporting RTL tab ordering with correctly mirrored navigation, and building accessible tab bars with announcements in the active language.
Understanding CupertinoTabBar in Localization Context
CupertinoTabBar renders an iOS-styled bottom navigation bar with icon-label pairs for each tab. For multilingual apps, this enables:
- Translated tab labels that fit iOS design conventions
- Localized badge values on tab icons
- RTL-aware tab ordering that mirrors left-to-right defaults
- Accessible tab names announced in the active language
Why CupertinoTabBar Matters for Multilingual Apps
CupertinoTabBar provides:
- iOS consistency: Tab navigation matching native iOS patterns in every language
- Compact labels: Short translated tab names that fit the tab bar width
- Badge support: Localized notification badges on tab icons
- Platform feel: iOS users expect Cupertino-style navigation regardless of language
Basic CupertinoTabBar Implementation
import 'package:flutter/cupertino.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class LocalizedCupertinoTabBarExample extends StatelessWidget {
const LocalizedCupertinoTabBarExample({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return CupertinoTabScaffold(
tabBar: CupertinoTabBar(
items: [
BottomNavigationBarItem(
icon: const Icon(CupertinoIcons.home),
label: l10n.homeTab,
),
BottomNavigationBarItem(
icon: const Icon(CupertinoIcons.search),
label: l10n.searchTab,
),
BottomNavigationBarItem(
icon: const Icon(CupertinoIcons.heart),
label: l10n.favoritesTab,
),
BottomNavigationBarItem(
icon: const Icon(CupertinoIcons.person),
label: l10n.profileTab,
),
],
),
tabBuilder: (context, index) {
return CupertinoTabView(
builder: (context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text(_tabTitle(l10n, index)),
),
child: Center(
child: Text(_tabTitle(l10n, index)),
),
);
},
);
},
);
}
String _tabTitle(AppLocalizations l10n, int index) {
return switch (index) {
0 => l10n.homeTitle,
1 => l10n.searchTitle,
2 => l10n.favoritesTitle,
3 => l10n.profileTitle,
_ => '',
};
}
}
Advanced CupertinoTabBar Patterns for Localization
Tab Bar with Badge Counts
CupertinoTabBar with localized badge numbers that format according to the active locale.
class BadgedTabBarExample extends StatefulWidget {
const BadgedTabBarExample({super.key});
@override
State<BadgedTabBarExample> createState() => _BadgedTabBarExampleState();
}
class _BadgedTabBarExampleState extends State<BadgedTabBarExample> {
int _unreadMessages = 5;
int _pendingOrders = 2;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return CupertinoTabScaffold(
tabBar: CupertinoTabBar(
items: [
BottomNavigationBarItem(
icon: const Icon(CupertinoIcons.home),
label: l10n.homeTab,
),
BottomNavigationBarItem(
icon: Badge(
label: Text('$_unreadMessages'),
child: const Icon(CupertinoIcons.chat_bubble),
),
label: l10n.messagesTab,
),
BottomNavigationBarItem(
icon: Badge(
label: Text('$_pendingOrders'),
child: const Icon(CupertinoIcons.cart),
),
label: l10n.ordersTab,
),
BottomNavigationBarItem(
icon: const Icon(CupertinoIcons.settings),
label: l10n.settingsTab,
),
],
),
tabBuilder: (context, index) {
return CupertinoTabView(
builder: (context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text(_tabTitle(l10n, index)),
),
child: Center(
child: CupertinoButton(
onPressed: () {
setState(() {
_unreadMessages = 0;
_pendingOrders = 0;
});
},
child: Text(l10n.clearAllLabel),
),
),
);
},
);
},
);
}
String _tabTitle(AppLocalizations l10n, int index) {
return switch (index) {
0 => l10n.homeTitle,
1 => l10n.messagesTitle,
2 => l10n.ordersTitle,
3 => l10n.settingsTitle,
_ => '',
};
}
}
E-Commerce Tab Navigation
A CupertinoTabBar for an e-commerce app with translated category tabs.
class ECommerceTabBar extends StatelessWidget {
const ECommerceTabBar({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return CupertinoTabScaffold(
tabBar: CupertinoTabBar(
activeColor: CupertinoColors.activeOrange,
items: [
BottomNavigationBarItem(
icon: const Icon(CupertinoIcons.house),
label: l10n.shopTab,
),
BottomNavigationBarItem(
icon: const Icon(CupertinoIcons.square_grid_2x2),
label: l10n.categoriesTab,
),
BottomNavigationBarItem(
icon: const Icon(CupertinoIcons.cart),
label: l10n.cartTab,
),
BottomNavigationBarItem(
icon: const Icon(CupertinoIcons.bell),
label: l10n.notificationsTab,
),
BottomNavigationBarItem(
icon: const Icon(CupertinoIcons.person),
label: l10n.accountTab,
),
],
),
tabBuilder: (context, index) {
return CupertinoTabView(
builder: (context) {
final title = switch (index) {
0 => l10n.shopTitle,
1 => l10n.categoriesTitle,
2 => l10n.cartTitle,
3 => l10n.notificationsTitle,
4 => l10n.accountTitle,
_ => '',
};
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text(title),
trailing: index == 0
? CupertinoButton(
padding: EdgeInsets.zero,
child: const Icon(CupertinoIcons.search),
onPressed: () {},
)
: null,
),
child: Center(child: Text(title)),
);
},
);
},
);
}
}
Social Media Tab Navigation
A CupertinoTabBar for a social app with translated labels and active tab indicators.
class SocialMediaTabBar extends StatelessWidget {
const SocialMediaTabBar({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return CupertinoTabScaffold(
tabBar: CupertinoTabBar(
items: [
BottomNavigationBarItem(
icon: const Icon(CupertinoIcons.news),
activeIcon: const Icon(CupertinoIcons.news_solid),
label: l10n.feedTab,
),
BottomNavigationBarItem(
icon: const Icon(CupertinoIcons.compass),
activeIcon: const Icon(CupertinoIcons.compass_fill),
label: l10n.exploreTab,
),
BottomNavigationBarItem(
icon: const Icon(CupertinoIcons.plus_app),
activeIcon: const Icon(CupertinoIcons.plus_app_fill),
label: l10n.createTab,
),
BottomNavigationBarItem(
icon: const Icon(CupertinoIcons.bell),
activeIcon: const Icon(CupertinoIcons.bell_fill),
label: l10n.activityTab,
),
BottomNavigationBarItem(
icon: const Icon(CupertinoIcons.person),
activeIcon: const Icon(CupertinoIcons.person_fill),
label: l10n.profileTab,
),
],
),
tabBuilder: (context, index) {
return CupertinoTabView(
builder: (context) {
return CupertinoPageScaffold(
child: Center(
child: Text(
switch (index) {
0 => l10n.feedTitle,
1 => l10n.exploreTitle,
2 => l10n.createTitle,
3 => l10n.activityTitle,
4 => l10n.profileTitle,
_ => '',
},
),
),
);
},
);
},
);
}
}
RTL Support and Bidirectional Layouts
CupertinoTabBar displays tabs in the same order regardless of text direction, but the text labels within each tab align correctly for RTL languages. Tab content pages should use directional properties.
class BidirectionalCupertinoTabBar extends StatelessWidget {
const BidirectionalCupertinoTabBar({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return CupertinoTabScaffold(
tabBar: CupertinoTabBar(
items: [
BottomNavigationBarItem(
icon: const Icon(CupertinoIcons.home),
label: l10n.homeTab,
),
BottomNavigationBarItem(
icon: const Icon(CupertinoIcons.search),
label: l10n.searchTab,
),
BottomNavigationBarItem(
icon: const Icon(CupertinoIcons.person),
label: l10n.profileTab,
),
],
),
tabBuilder: (context, index) {
return CupertinoTabView(
builder: (context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text(l10n.homeTitle),
),
child: Padding(
padding: const EdgeInsetsDirectional.all(16),
child: Align(
alignment: AlignmentDirectional.centerStart,
child: Text(l10n.welcomeMessage),
),
),
);
},
);
},
);
}
}
Testing CupertinoTabBar Localization
import 'package:flutter/cupertino.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
void main() {
Widget buildTestWidget({Locale locale = const Locale('en')}) {
return CupertinoApp(
locale: locale,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: const LocalizedCupertinoTabBarExample(),
);
}
testWidgets('CupertinoTabBar shows localized labels', (tester) async {
await tester.pumpWidget(buildTestWidget());
await tester.pumpAndSettle();
expect(find.byType(CupertinoTabBar), findsOneWidget);
});
testWidgets('CupertinoTabBar works in RTL', (tester) async {
await tester.pumpWidget(buildTestWidget(locale: const Locale('ar')));
await tester.pumpAndSettle();
expect(tester.takeException(), isNull);
});
}
Best Practices
Keep tab labels short — iOS tab bars have limited horizontal space, so use concise translated labels (1-2 words maximum).
Use
activeIconalongsideiconso both the active and inactive states have appropriate icons regardless of the language displayed.Combine with
CupertinoTabScaffoldandCupertinoTabViewfor proper tab-based navigation with independent navigation stacks per tab.Format badge numbers using the active locale when displaying notification counts on tab icons.
Test label truncation with longer translations (German, Finnish) to ensure tab labels remain readable and do not overflow.
Use
CupertinoPageScaffoldinside each tab for consistent iOS styling with translated navigation bar titles.
Conclusion
CupertinoTabBar provides iOS-style bottom tab navigation for Flutter apps. For multilingual apps, it handles translated tab labels and badge counts, supports active and inactive icon states, and pairs with CupertinoTabScaffold for full tab navigation. By keeping labels short, formatting badges for the active locale, and testing with verbose translations, you can build iOS tab bars that work correctly in every supported language.