Flutter CupertinoActionSheet Localization: iOS-Style Bottom Sheets for Multilingual Apps
CupertinoActionSheet is a Flutter widget that displays an iOS-style action sheet sliding up from the bottom of the screen with a list of options. In multilingual applications, CupertinoActionSheet is essential for presenting translated action options, showing localized destructive action warnings, providing a translated cancel button, and maintaining iOS bottom sheet conventions across all supported locales.
Understanding CupertinoActionSheet in Localization Context
CupertinoActionSheet renders a translucent bottom sheet with an optional title, message, a list of action buttons, and a separated cancel button. For multilingual apps, this enables:
- Translated action labels for share, delete, copy, and custom operations
- Localized title and message text explaining the context of the actions
- A translated cancel button that always appears separated at the bottom
- Destructive action styling with translated warning labels
Why CupertinoActionSheet Matters for Multilingual Apps
CupertinoActionSheet provides:
- iOS-native presentation: Bottom sheet with blur and slide animation matching iOS conventions
- Action organization: Multiple translated options with destructive/default styling
- Cancel button: Always-present translated cancel that dismisses the sheet
- Context messaging: Title and message area for translated explanations
Basic CupertinoActionSheet Implementation
import 'package:flutter/cupertino.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class LocalizedCupertinoActionSheetExample extends StatelessWidget {
const LocalizedCupertinoActionSheetExample({super.key});
void _showActionSheet(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
showCupertinoModalPopup(
context: context,
builder: (context) {
final sheetL10n = AppLocalizations.of(context)!;
return CupertinoActionSheet(
title: Text(sheetL10n.photoOptionsTitle),
message: Text(sheetL10n.photoOptionsMessage),
actions: [
CupertinoActionSheetAction(
onPressed: () => Navigator.pop(context, 'camera'),
child: Text(sheetL10n.takePhotoAction),
),
CupertinoActionSheetAction(
onPressed: () => Navigator.pop(context, 'gallery'),
child: Text(sheetL10n.chooseFromGalleryAction),
),
CupertinoActionSheetAction(
onPressed: () => Navigator.pop(context, 'files'),
child: Text(sheetL10n.browseFilesAction),
),
],
cancelButton: CupertinoActionSheetAction(
isDefaultAction: true,
onPressed: () => Navigator.pop(context),
child: Text(sheetL10n.cancelButton),
),
);
},
);
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text(l10n.profileTitle),
),
child: SafeArea(
child: Center(
child: CupertinoButton.filled(
onPressed: () => _showActionSheet(context),
child: Text(l10n.changePhotoButton),
),
),
),
);
}
}
Advanced CupertinoActionSheet Patterns for Localization
Share Sheet with Localized Options
A sharing action sheet with translated sharing destinations and a localized title.
class LocalizedShareSheet extends StatelessWidget {
const LocalizedShareSheet({super.key});
void _showShareSheet(BuildContext context) {
showCupertinoModalPopup(
context: context,
builder: (context) {
final l10n = AppLocalizations.of(context)!;
return CupertinoActionSheet(
title: Text(l10n.shareTitle),
message: Text(l10n.shareMessage),
actions: [
CupertinoActionSheetAction(
onPressed: () => Navigator.pop(context, 'message'),
child: Text(l10n.shareViaMessageAction),
),
CupertinoActionSheetAction(
onPressed: () => Navigator.pop(context, 'email'),
child: Text(l10n.shareViaEmailAction),
),
CupertinoActionSheetAction(
onPressed: () => Navigator.pop(context, 'link'),
child: Text(l10n.copyLinkAction),
),
CupertinoActionSheetAction(
onPressed: () => Navigator.pop(context, 'more'),
child: Text(l10n.moreOptionsAction),
),
],
cancelButton: CupertinoActionSheetAction(
isDefaultAction: true,
onPressed: () => Navigator.pop(context),
child: Text(l10n.cancelButton),
),
);
},
);
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Center(
child: CupertinoButton(
onPressed: () => _showShareSheet(context),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(CupertinoIcons.share),
const SizedBox(width: 8),
Text(l10n.shareButton),
],
),
),
);
}
}
Destructive Action Sheet with Warning
Action sheets for destructive operations with a translated warning message and confirmation.
class DestructiveActionSheet extends StatelessWidget {
const DestructiveActionSheet({super.key});
void _showDeleteOptions(BuildContext context, String itemName) {
showCupertinoModalPopup(
context: context,
builder: (context) {
final l10n = AppLocalizations.of(context)!;
return CupertinoActionSheet(
title: Text(l10n.deleteOptionsTitle),
message: Text(l10n.deleteWarningMessage(itemName)),
actions: [
CupertinoActionSheetAction(
onPressed: () => Navigator.pop(context, 'archive'),
child: Text(l10n.archiveInsteadAction),
),
CupertinoActionSheetAction(
onPressed: () => Navigator.pop(context, 'hide'),
child: Text(l10n.hideAction),
),
CupertinoActionSheetAction(
isDestructiveAction: true,
onPressed: () => Navigator.pop(context, 'delete'),
child: Text(l10n.deletePermanentlyAction),
),
],
cancelButton: CupertinoActionSheetAction(
isDefaultAction: true,
onPressed: () => Navigator.pop(context),
child: Text(l10n.cancelButton),
),
);
},
);
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Center(
child: CupertinoButton(
color: CupertinoColors.destructiveRed,
onPressed: () => _showDeleteOptions(context, l10n.sampleDocumentName),
child: Text(l10n.deleteButton),
),
);
}
}
Sort/Filter Action Sheet
A sort options sheet where each option shows a translated label and the current selection.
class LocalizedSortSheet extends StatefulWidget {
const LocalizedSortSheet({super.key});
@override
State<LocalizedSortSheet> createState() => _LocalizedSortSheetState();
}
class _LocalizedSortSheetState extends State<LocalizedSortSheet> {
String _sortBy = 'date';
void _showSortOptions() {
showCupertinoModalPopup(
context: context,
builder: (context) {
final l10n = AppLocalizations.of(context)!;
return CupertinoActionSheet(
title: Text(l10n.sortByTitle),
actions: [
CupertinoActionSheetAction(
onPressed: () {
setState(() => _sortBy = 'date');
Navigator.pop(context);
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(l10n.sortByDateAction),
if (_sortBy == 'date') ...[
const SizedBox(width: 8),
const Icon(CupertinoIcons.checkmark,
size: 18, color: CupertinoColors.activeBlue),
],
],
),
),
CupertinoActionSheetAction(
onPressed: () {
setState(() => _sortBy = 'name');
Navigator.pop(context);
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(l10n.sortByNameAction),
if (_sortBy == 'name') ...[
const SizedBox(width: 8),
const Icon(CupertinoIcons.checkmark,
size: 18, color: CupertinoColors.activeBlue),
],
],
),
),
CupertinoActionSheetAction(
onPressed: () {
setState(() => _sortBy = 'size');
Navigator.pop(context);
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(l10n.sortBySizeAction),
if (_sortBy == 'size') ...[
const SizedBox(width: 8),
const Icon(CupertinoIcons.checkmark,
size: 18, color: CupertinoColors.activeBlue),
],
],
),
),
],
cancelButton: CupertinoActionSheetAction(
isDefaultAction: true,
onPressed: () => Navigator.pop(context),
child: Text(l10n.cancelButton),
),
);
},
);
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Center(
child: CupertinoButton(
onPressed: _showSortOptions,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(CupertinoIcons.sort_down),
const SizedBox(width: 8),
Text(l10n.sortButton),
],
),
),
);
}
}
RTL Support and Bidirectional Layouts
CupertinoActionSheet content automatically adapts to RTL. Action text aligns correctly, and the sheet's title and message follow the active directionality.
class BidirectionalActionSheet extends StatelessWidget {
const BidirectionalActionSheet({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Center(
child: CupertinoButton.filled(
onPressed: () {
showCupertinoModalPopup(
context: context,
builder: (context) {
final sheetL10n = AppLocalizations.of(context)!;
return CupertinoActionSheet(
title: Text(sheetL10n.optionsTitle),
message: Text(sheetL10n.optionsMessage),
actions: [
CupertinoActionSheetAction(
onPressed: () => Navigator.pop(context),
child: Text(sheetL10n.editAction),
),
CupertinoActionSheetAction(
onPressed: () => Navigator.pop(context),
child: Text(sheetL10n.duplicateAction),
),
CupertinoActionSheetAction(
isDestructiveAction: true,
onPressed: () => Navigator.pop(context),
child: Text(sheetL10n.deleteAction),
),
],
cancelButton: CupertinoActionSheetAction(
isDefaultAction: true,
onPressed: () => Navigator.pop(context),
child: Text(sheetL10n.cancelButton),
),
);
},
);
},
child: Text(l10n.showOptionsButton),
),
);
}
}
Testing CupertinoActionSheet 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 LocalizedCupertinoActionSheetExample(),
);
}
testWidgets('CupertinoActionSheet shows localized actions', (tester) async {
await tester.pumpWidget(buildTestWidget());
await tester.pumpAndSettle();
await tester.tap(find.byType(CupertinoButton).first);
await tester.pumpAndSettle();
expect(find.byType(CupertinoActionSheet), findsOneWidget);
});
testWidgets('CupertinoActionSheet works in RTL', (tester) async {
await tester.pumpWidget(buildTestWidget(locale: const Locale('ar')));
await tester.pumpAndSettle();
expect(tester.takeException(), isNull);
});
}
Best Practices
Always provide a cancel button with a translated label -- it appears separated from the action list and is expected by iOS users.
Use
isDestructiveAction: truefor delete/remove actions so the label renders in red, immediately signaling danger in any language.Use parameterized messages in the title or message area to include dynamic item names, counts, or context in the translated text.
Show checkmarks for the current selection in sort/filter sheets so users see their active choice regardless of language.
Use
showCupertinoModalPopup(notshowCupertinoDialog) to present action sheets with the correct iOS slide-up animation.Test with many actions to verify that the sheet scrolls correctly when translated labels produce a long list.
Conclusion
CupertinoActionSheet is the standard iOS bottom sheet for presenting action options in Flutter. For multilingual apps, it handles translated action labels, destructive warnings, parameterized messages, and cancel buttons while maintaining the expected iOS slide-up presentation. By combining action sheets with sort options, share menus, and destructive confirmations, you can build iOS-native option sheets that communicate clearly in every supported language.