Flutter CupertinoSlidingSegmentedControl Localization: iOS Segmented Controls for Multilingual Apps
CupertinoSlidingSegmentedControl is a Flutter widget that renders an iOS 13-style sliding segmented control for switching between related views. In multilingual applications, CupertinoSlidingSegmentedControl is essential for displaying translated segment labels that match iOS platform conventions, handling variable-width segments for different language lengths, supporting RTL segment ordering with correctly positioned sliding thumb, and building accessible segmented controls with announcements in the active language.
Understanding CupertinoSlidingSegmentedControl in Localization Context
CupertinoSlidingSegmentedControl renders an iOS-styled segmented control with a sliding thumb animation. For multilingual apps, this enables:
- Translated segment labels that adjust width automatically
- Localized view switching between translated content sections
- RTL-aware segment ordering and thumb positioning
- Accessible segment names announced in the active language
Why CupertinoSlidingSegmentedControl Matters for Multilingual Apps
CupertinoSlidingSegmentedControl provides:
- iOS consistency: Segmented controls matching native iOS patterns in every language
- Flexible sizing: Segments auto-size to fit translated labels of varying lengths
- Smooth animation: Sliding thumb transitions between segments regardless of text direction
- Platform feel: iOS users expect Cupertino-style segmented controls in all languages
Basic CupertinoSlidingSegmentedControl Implementation
import 'package:flutter/cupertino.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class LocalizedSlidingSegmentedExample extends StatefulWidget {
const LocalizedSlidingSegmentedExample({super.key});
@override
State<LocalizedSlidingSegmentedExample> createState() =>
_LocalizedSlidingSegmentedExampleState();
}
class _LocalizedSlidingSegmentedExampleState
extends State<LocalizedSlidingSegmentedExample> {
int _selectedSegment = 0;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text(l10n.inboxTitle),
),
child: SafeArea(
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(16),
child: CupertinoSlidingSegmentedControl<int>(
groupValue: _selectedSegment,
onValueChanged: (value) {
setState(() => _selectedSegment = value!);
},
children: {
0: Text(l10n.allLabel),
1: Text(l10n.unreadLabel),
2: Text(l10n.starredLabel),
},
),
),
Expanded(
child: Center(
child: Text(
switch (_selectedSegment) {
0 => l10n.allMessagesContent,
1 => l10n.unreadMessagesContent,
2 => l10n.starredMessagesContent,
_ => '',
},
),
),
),
],
),
),
);
}
}
Advanced CupertinoSlidingSegmentedControl Patterns for Localization
Content Filter with Icon Segments
A segmented control with icon-label pairs for filtering content views.
class IconSegmentedFilter extends StatefulWidget {
const IconSegmentedFilter({super.key});
@override
State<IconSegmentedFilter> createState() => _IconSegmentedFilterState();
}
class _IconSegmentedFilterState extends State<IconSegmentedFilter> {
int _viewMode = 0;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text(l10n.filesTitle),
),
child: SafeArea(
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(16),
child: CupertinoSlidingSegmentedControl<int>(
groupValue: _viewMode,
onValueChanged: (value) {
setState(() => _viewMode = value!);
},
children: {
0: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(CupertinoIcons.list_bullet, size: 16),
const SizedBox(width: 6),
Text(l10n.listViewLabel),
],
),
1: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(CupertinoIcons.square_grid_2x2, size: 16),
const SizedBox(width: 6),
Text(l10n.gridViewLabel),
],
),
2: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(CupertinoIcons.square_stack, size: 16),
const SizedBox(width: 6),
Text(l10n.columnsViewLabel),
],
),
},
),
),
Expanded(
child: Center(
child: Text(
switch (_viewMode) {
0 => l10n.listViewDescription,
1 => l10n.gridViewDescription,
2 => l10n.columnsViewDescription,
_ => '',
},
),
),
),
],
),
),
);
}
}
Time Period Selector
A segmented control for selecting time periods with translated labels.
class TimePeriodSelector extends StatefulWidget {
const TimePeriodSelector({super.key});
@override
State<TimePeriodSelector> createState() => _TimePeriodSelectorState();
}
class _TimePeriodSelectorState extends State<TimePeriodSelector> {
String _period = 'day';
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text(l10n.analyticsTitle),
),
child: SafeArea(
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(16),
child: CupertinoSlidingSegmentedControl<String>(
groupValue: _period,
onValueChanged: (value) {
setState(() => _period = value!);
},
children: {
'day': Text(l10n.dayLabel),
'week': Text(l10n.weekLabel),
'month': Text(l10n.monthLabel),
'year': Text(l10n.yearLabel),
},
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
l10n.periodSummaryTitle(_period),
style: CupertinoTheme.of(context)
.textTheme
.navTitleTextStyle,
),
const SizedBox(height: 8),
Text(l10n.periodDataDescription),
],
),
),
),
],
),
),
);
}
}
Settings Preference Selector
A segmented control embedded in a settings page for selecting localized preferences.
class SettingsPreferenceSelector extends StatefulWidget {
const SettingsPreferenceSelector({super.key});
@override
State<SettingsPreferenceSelector> createState() =>
_SettingsPreferenceSelectorState();
}
class _SettingsPreferenceSelectorState
extends State<SettingsPreferenceSelector> {
int _theme = 0;
int _textSize = 1;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text(l10n.displaySettingsTitle),
),
child: SafeArea(
child: ListView(
padding: const EdgeInsets.all(16),
children: [
CupertinoListSection.insetGrouped(
header: Text(l10n.appearanceHeader),
children: [
CupertinoListTile(
title: Text(l10n.themeLabel),
additionalInfo: SizedBox(
width: 240,
child: CupertinoSlidingSegmentedControl<int>(
groupValue: _theme,
onValueChanged: (value) {
setState(() => _theme = value!);
},
children: {
0: Text(l10n.lightLabel),
1: Text(l10n.darkLabel),
2: Text(l10n.autoLabel),
},
),
),
),
],
),
const SizedBox(height: 16),
CupertinoListSection.insetGrouped(
header: Text(l10n.readingHeader),
children: [
CupertinoListTile(
title: Text(l10n.textSizeLabel),
additionalInfo: SizedBox(
width: 240,
child: CupertinoSlidingSegmentedControl<int>(
groupValue: _textSize,
onValueChanged: (value) {
setState(() => _textSize = value!);
},
children: {
0: Text(l10n.smallLabel),
1: Text(l10n.mediumLabel),
2: Text(l10n.largeLabel),
},
),
),
),
],
),
],
),
),
);
}
}
RTL Support and Bidirectional Layouts
CupertinoSlidingSegmentedControl handles RTL text within each segment automatically. The segment labels render with correct text direction while the sliding thumb animation works in both directions.
class BidirectionalSlidingSegmented extends StatefulWidget {
const BidirectionalSlidingSegmented({super.key});
@override
State<BidirectionalSlidingSegmented> createState() =>
_BidirectionalSlidingSegmentedState();
}
class _BidirectionalSlidingSegmentedState
extends State<BidirectionalSlidingSegmented> {
int _selected = 0;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text(l10n.settingsTitle),
),
child: SafeArea(
child: Padding(
padding: const EdgeInsetsDirectional.all(16),
child: Column(
children: [
CupertinoSlidingSegmentedControl<int>(
groupValue: _selected,
onValueChanged: (value) {
setState(() => _selected = value!);
},
children: {
0: Text(l10n.generalLabel),
1: Text(l10n.privacyLabel),
2: Text(l10n.advancedLabel),
},
),
const SizedBox(height: 24),
Align(
alignment: AlignmentDirectional.centerStart,
child: Text(
switch (_selected) {
0 => l10n.generalSettingsDescription,
1 => l10n.privacySettingsDescription,
2 => l10n.advancedSettingsDescription,
_ => '',
},
),
),
],
),
),
),
);
}
}
Testing CupertinoSlidingSegmentedControl 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 LocalizedSlidingSegmentedExample(),
);
}
testWidgets('Segmented control shows localized labels', (tester) async {
await tester.pumpWidget(buildTestWidget());
await tester.pumpAndSettle();
expect(
find.byType(CupertinoSlidingSegmentedControl<int>),
findsOneWidget,
);
});
testWidgets('Segmented control works in RTL', (tester) async {
await tester.pumpWidget(buildTestWidget(locale: const Locale('ar')));
await tester.pumpAndSettle();
expect(tester.takeException(), isNull);
});
}
Best Practices
Keep segment labels concise — use 1-2 word translations since segments share horizontal space equally and long text will shrink the font.
Use icon-label pairs for segments when labels alone may be ambiguous across languages, providing visual context alongside translated text.
Test with verbose translations to verify the control handles longer labels gracefully without layout overflow or unreadable text.
Embed in
CupertinoListSectionwhen placing segmented controls inside settings pages for consistent iOS-style grouped list appearance.Use typed generic parameters like
CupertinoSlidingSegmentedControl<String>for meaningful segment values instead of raw integers.Pair with
EdgeInsetsDirectionalfor surrounding padding to ensure the control positions correctly in both LTR and RTL layouts.
Conclusion
CupertinoSlidingSegmentedControl provides an iOS 13-style segmented control for Flutter apps. For multilingual apps, it handles translated segment labels with auto-sizing, supports icon-label pairs for visual context, and works with RTL text direction. By keeping labels short, combining with CupertinoListSection for settings pages, and testing with long translations, you can build segmented controls that function correctly in every supported language.