← Back to Blog

Flutter CupertinoListSection Localization: iOS List Sections for Multilingual Apps

fluttercupertinolistsectionioslocalizationrtl

Flutter CupertinoListSection Localization: iOS List Sections for Multilingual Apps

CupertinoListSection is a Flutter widget that renders an iOS-style grouped list section with an optional header, footer, and a collection of CupertinoListTile children. In multilingual applications, CupertinoListSection is essential for displaying translated section headers that categorize list items, grouping localized list tiles into visually distinct iOS-style sections, supporting RTL text alignment for section headers and footers in Arabic and Hebrew, and building accessible list structures with section announcements in the active language.

Understanding CupertinoListSection in Localization Context

CupertinoListSection groups CupertinoListTile widgets into rounded, inset sections following iOS design patterns. For multilingual apps, this enables:

  • Translated section headers (Favorites, Recent, Settings) above each group
  • Localized footer descriptions beneath sections
  • RTL-compatible header alignment and list tile ordering
  • Accessible section group labels in the active language

Why CupertinoListSection Matters for Multilingual Apps

CupertinoListSection provides:

  • iOS consistency: List grouping matching native iOS Settings and list patterns in every language
  • Visual hierarchy: Translated headers create clear category boundaries for list items
  • Inset styling: Rounded section backgrounds that match iOS 15+ design across locales
  • Platform feel: iOS users expect grouped list sections regardless of language

Basic CupertinoListSection Implementation

import 'package:flutter/cupertino.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

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

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;

    return CupertinoPageScaffold(
      navigationBar: CupertinoNavigationBar(
        middle: Text(l10n.settingsTitle),
      ),
      child: SafeArea(
        child: ListView(
          children: [
            CupertinoListSection.insetGrouped(
              header: Text(l10n.generalHeader),
              children: [
                CupertinoListTile(
                  title: Text(l10n.aboutLabel),
                  leading: const Icon(CupertinoIcons.info),
                  trailing: const CupertinoListTileChevron(),
                ),
                CupertinoListTile(
                  title: Text(l10n.softwareUpdateLabel),
                  leading: const Icon(CupertinoIcons.arrow_down_circle),
                  trailing: const CupertinoListTileChevron(),
                ),
              ],
            ),
            CupertinoListSection.insetGrouped(
              header: Text(l10n.connectivityHeader),
              children: [
                CupertinoListTile(
                  title: Text(l10n.wifiLabel),
                  leading: const Icon(CupertinoIcons.wifi),
                  additionalInfo: Text(l10n.connectedStatus),
                  trailing: const CupertinoListTileChevron(),
                ),
                CupertinoListTile(
                  title: Text(l10n.bluetoothLabel),
                  leading: const Icon(CupertinoIcons.bluetooth),
                  additionalInfo: Text(l10n.onStatus),
                  trailing: const CupertinoListTileChevron(),
                ),
                CupertinoListTile(
                  title: Text(l10n.cellularLabel),
                  leading: const Icon(CupertinoIcons.antenna_radiowaves_left_right),
                  trailing: const CupertinoListTileChevron(),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

Advanced CupertinoListSection Patterns for Localization

Contact List with Alphabetical Sections

CupertinoListSection used to group contacts by localized alphabetical headers.

class ContactListExample extends StatelessWidget {
  final Map<String, List<String>> groupedContacts;

  const ContactListExample({
    super.key,
    required this.groupedContacts,
  });

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;

    return CupertinoPageScaffold(
      navigationBar: CupertinoNavigationBar(
        middle: Text(l10n.contactsTitle),
      ),
      child: SafeArea(
        child: ListView(
          children: [
            for (final entry in groupedContacts.entries)
              CupertinoListSection.insetGrouped(
                header: Text(entry.key),
                children: [
                  for (final contact in entry.value)
                    CupertinoListTile(
                      title: Text(contact),
                      leading: const Icon(CupertinoIcons.person_circle),
                      trailing: const CupertinoListTileChevron(),
                      onTap: () {},
                    ),
                ],
              ),
          ],
        ),
      ),
    );
  }
}

App Settings with Categories

CupertinoListSection for an app settings page with localized category sections.

class AppSettingsExample extends StatefulWidget {
  const AppSettingsExample({super.key});

  @override
  State<AppSettingsExample> createState() => _AppSettingsExampleState();
}

class _AppSettingsExampleState extends State<AppSettingsExample> {
  bool _soundEnabled = true;
  bool _hapticEnabled = true;
  bool _autoSave = false;

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;

    return CupertinoPageScaffold(
      navigationBar: CupertinoNavigationBar(
        middle: Text(l10n.appSettingsTitle),
      ),
      child: SafeArea(
        child: ListView(
          children: [
            CupertinoListSection.insetGrouped(
              header: Text(l10n.appearanceHeader),
              children: [
                CupertinoListTile(
                  title: Text(l10n.themeLabel),
                  leading: const Icon(CupertinoIcons.paintbrush),
                  additionalInfo: Text(l10n.systemTheme),
                  trailing: const CupertinoListTileChevron(),
                  onTap: () {},
                ),
                CupertinoListTile(
                  title: Text(l10n.textSizeLabel),
                  leading: const Icon(CupertinoIcons.textformat_size),
                  additionalInfo: Text(l10n.mediumSize),
                  trailing: const CupertinoListTileChevron(),
                  onTap: () {},
                ),
                CupertinoListTile(
                  title: Text(l10n.languageLabel),
                  leading: const Icon(CupertinoIcons.globe),
                  additionalInfo: Text(l10n.currentLanguage),
                  trailing: const CupertinoListTileChevron(),
                  onTap: () {},
                ),
              ],
            ),
            CupertinoListSection.insetGrouped(
              header: Text(l10n.soundAndHapticsHeader),
              footer: Text(l10n.soundAndHapticsFooter),
              children: [
                CupertinoListTile(
                  title: Text(l10n.soundEffectsLabel),
                  trailing: CupertinoSwitch(
                    value: _soundEnabled,
                    onChanged: (bool value) {
                      setState(() => _soundEnabled = value);
                    },
                  ),
                ),
                CupertinoListTile(
                  title: Text(l10n.hapticFeedbackLabel),
                  trailing: CupertinoSwitch(
                    value: _hapticEnabled,
                    onChanged: (bool value) {
                      setState(() => _hapticEnabled = value);
                    },
                  ),
                ),
              ],
            ),
            CupertinoListSection.insetGrouped(
              header: Text(l10n.dataHeader),
              footer: Text(l10n.dataFooter),
              children: [
                CupertinoListTile(
                  title: Text(l10n.autoSaveLabel),
                  trailing: CupertinoSwitch(
                    value: _autoSave,
                    onChanged: (bool value) {
                      setState(() => _autoSave = value);
                    },
                  ),
                ),
                CupertinoListTile(
                  title: Text(l10n.clearCacheLabel),
                  leading: const Icon(CupertinoIcons.trash),
                  trailing: const CupertinoListTileChevron(),
                  onTap: () {},
                ),
                CupertinoListTile(
                  title: Text(l10n.exportDataLabel),
                  leading: const Icon(CupertinoIcons.share),
                  trailing: const CupertinoListTileChevron(),
                  onTap: () {},
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

Music Library with Sections

CupertinoListSection for a music library organized by localized category sections.

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

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;

    return CupertinoPageScaffold(
      navigationBar: CupertinoNavigationBar(
        middle: Text(l10n.musicLibraryTitle),
      ),
      child: SafeArea(
        child: ListView(
          children: [
            CupertinoListSection.insetGrouped(
              header: Text(l10n.recentlyPlayedHeader),
              children: [
                CupertinoListTile(
                  title: Text(l10n.sampleSongTitle1),
                  subtitle: Text(l10n.sampleArtist1),
                  leading: Container(
                    width: 44,
                    height: 44,
                    decoration: BoxDecoration(
                      color: CupertinoColors.systemPurple,
                      borderRadius: BorderRadius.circular(6),
                    ),
                    child: const Icon(
                      CupertinoIcons.music_note,
                      color: CupertinoColors.white,
                    ),
                  ),
                  trailing: const CupertinoListTileChevron(),
                ),
                CupertinoListTile(
                  title: Text(l10n.sampleSongTitle2),
                  subtitle: Text(l10n.sampleArtist2),
                  leading: Container(
                    width: 44,
                    height: 44,
                    decoration: BoxDecoration(
                      color: CupertinoColors.systemOrange,
                      borderRadius: BorderRadius.circular(6),
                    ),
                    child: const Icon(
                      CupertinoIcons.music_note,
                      color: CupertinoColors.white,
                    ),
                  ),
                  trailing: const CupertinoListTileChevron(),
                ),
              ],
            ),
            CupertinoListSection.insetGrouped(
              header: Text(l10n.playlistsHeader),
              children: [
                CupertinoListTile(
                  title: Text(l10n.favoritesPlaylist),
                  leading: const Icon(CupertinoIcons.heart_fill),
                  additionalInfo: Text(l10n.songCount(42)),
                  trailing: const CupertinoListTileChevron(),
                ),
                CupertinoListTile(
                  title: Text(l10n.workoutPlaylist),
                  leading: const Icon(CupertinoIcons.bolt_fill),
                  additionalInfo: Text(l10n.songCount(18)),
                  trailing: const CupertinoListTileChevron(),
                ),
                CupertinoListTile(
                  title: Text(l10n.relaxPlaylist),
                  leading: const Icon(CupertinoIcons.moon_fill),
                  additionalInfo: Text(l10n.songCount(25)),
                  trailing: const CupertinoListTileChevron(),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

RTL Support and Bidirectional Layouts

CupertinoListSection aligns headers and footers according to the active text direction. List tiles within each section also respect RTL, placing leading widgets on the right and trailing widgets on the left for right-to-left languages.

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

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;

    return CupertinoPageScaffold(
      navigationBar: CupertinoNavigationBar(
        middle: Text(l10n.settingsTitle),
      ),
      child: SafeArea(
        child: ListView(
          children: [
            CupertinoListSection.insetGrouped(
              header: Text(l10n.generalHeader),
              footer: Text(l10n.generalFooter),
              children: [
                CupertinoListTile(
                  title: Text(l10n.languageLabel),
                  leading: const Icon(CupertinoIcons.globe),
                  trailing: const CupertinoListTileChevron(),
                ),
                CupertinoListTile(
                  title: Text(l10n.regionLabel),
                  leading: const Icon(CupertinoIcons.map),
                  trailing: const CupertinoListTileChevron(),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

Testing CupertinoListSection 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 LocalizedCupertinoListSectionExample(),
    );
  }

  testWidgets('CupertinoListSection renders with headers', (tester) async {
    await tester.pumpWidget(buildTestWidget());
    await tester.pumpAndSettle();
    expect(find.byType(CupertinoListSection), findsWidgets);
  });

  testWidgets('CupertinoListSection works in RTL', (tester) async {
    await tester.pumpWidget(buildTestWidget(locale: const Locale('ar')));
    await tester.pumpAndSettle();
    expect(tester.takeException(), isNull);
  });

  testWidgets('CupertinoListSection renders in Korean', (tester) async {
    await tester.pumpWidget(buildTestWidget(locale: const Locale('ko')));
    await tester.pumpAndSettle();
    expect(find.byType(CupertinoListSection), findsWidgets);
  });
}

Best Practices

  1. Use insetGrouped constructor for modern iOS-style rounded sections with inset margins, matching iOS 15+ design conventions across all languages.

  2. Keep headers short — Section headers should be 1-3 words in all supported languages to maintain a clean visual hierarchy.

  3. Use additionalInfo on CupertinoListTile to show localized status text (Connected, On, 42 songs) aligned to the trailing side.

  4. Provide footer descriptions — Use translated footer text beneath sections to explain settings behavior or provide context.

  5. Combine with CupertinoListTile as direct children for consistent iOS list styling with proper separators between items.

  6. Test with multiple locales including RTL (Arabic), verbose (German), and compact (Chinese) to verify section layout adapts correctly.

Conclusion

CupertinoListSection provides iOS-style grouped list sections for Flutter apps. For multilingual apps, it handles translated section headers and footers, groups localized list tiles into visually distinct sections, and supports RTL alignment for right-to-left languages. By using the insetGrouped constructor, combining with CupertinoListTile, and testing across diverse locales, you can build list layouts that match native iOS patterns in every supported language.

Further Reading