Flutter SliverList Localization: Scrollable Lists in Custom Scroll Views for Multilingual Apps
SliverList is a Flutter widget that creates a linear list of items inside a CustomScrollView. In multilingual applications, SliverList is essential for building scrollable lists with translated content that integrate with other slivers, creating lazy-loaded translated item lists that only build visible items, supporting RTL scroll alignment and text direction, and combining translated list sections with sliver headers and grids in a single scroll view.
Understanding SliverList in Localization Context
SliverList renders a list of children as a sliver, typically inside a CustomScrollView alongside other slivers like SliverAppBar or SliverGrid. For multilingual apps, this enables:
- Translated list items in a sliver-based scroll layout
- Lazy construction of localized items with
SliverList.builder - RTL-aware list alignment that respects text direction
- Combined sliver layouts with translated headers, lists, and grids
Why SliverList Matters for Multilingual Apps
SliverList provides:
- Sliver integration: Translated lists that scroll alongside app bars, headers, and grids
- Lazy building: Only visible items are built, efficient for long translated lists
- Flexible delegates:
SliverChildBuilderDelegateandSliverChildListDelegatefor different use cases - Separator support:
SliverList.separatedfor lists with dividers between translated items
Basic SliverList Implementation
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class LocalizedSliverListExample extends StatelessWidget {
const LocalizedSliverListExample({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
expandedHeight: 150,
pinned: true,
flexibleSpace: FlexibleSpaceBar(
title: Text(l10n.articlesTitle),
),
),
SliverList.builder(
itemCount: 20,
itemBuilder: (context, index) {
return ListTile(
leading: CircleAvatar(
child: Text('${index + 1}'),
),
title: Text('${l10n.articleLabel} ${index + 1}'),
subtitle: Text(l10n.articleDescription),
trailing: Icon(
Directionality.of(context) == TextDirection.rtl
? Icons.chevron_left
: Icons.chevron_right,
),
onTap: () {},
);
},
),
],
),
);
}
}
Advanced SliverList Patterns for Localization
Sectioned SliverList with Translated Headers
Multiple SliverLists separated by sticky translated section headers.
class SectionedSliverList extends StatelessWidget {
const SectionedSliverList({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final sections = [
_Section(l10n.todayLabel, [
l10n.meetingWithTeamItem,
l10n.codeReviewItem,
l10n.designFeedbackItem,
]),
_Section(l10n.tomorrowLabel, [
l10n.clientCallItem,
l10n.sprintPlanningItem,
]),
_Section(l10n.thisWeekLabel, [
l10n.releasePreparationItem,
l10n.documentationItem,
l10n.testingItem,
l10n.deploymentItem,
]),
];
return Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
pinned: true,
title: Text(l10n.tasksTitle),
),
for (final section in sections) ...[
SliverToBoxAdapter(
child: Container(
padding: const EdgeInsetsDirectional.fromSTEB(16, 16, 16, 8),
color: Theme.of(context).colorScheme.surfaceContainerHighest,
child: Text(
section.title,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
),
SliverList.builder(
itemCount: section.items.length,
itemBuilder: (context, index) {
return ListTile(
leading: Checkbox(
value: false,
onChanged: (value) {},
),
title: Text(section.items[index]),
);
},
),
],
],
),
);
}
}
class _Section {
final String title;
final List<String> items;
_Section(this.title, this.items);
}
SliverList with Separated Items
SliverList.separated with translated content and dividers between items.
class SeparatedSliverList extends StatelessWidget {
const SeparatedSliverList({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final notifications = List.generate(15, (index) {
return _Notification(
title: '${l10n.notificationLabel} ${index + 1}',
message: l10n.notificationMessage,
time: '${index + 1}${l10n.hoursAgoSuffix}',
isRead: index > 3,
);
});
return Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
pinned: true,
title: Text(l10n.notificationsTitle),
actions: [
TextButton(
onPressed: () {},
child: Text(l10n.markAllReadLabel),
),
],
),
SliverList.separated(
itemCount: notifications.length,
itemBuilder: (context, index) {
final notification = notifications[index];
return ListTile(
leading: CircleAvatar(
backgroundColor: notification.isRead
? Theme.of(context).colorScheme.surfaceContainerHighest
: Theme.of(context).colorScheme.primaryContainer,
child: Icon(
notification.isRead
? Icons.notifications_none
: Icons.notifications_active,
color: notification.isRead
? Theme.of(context).colorScheme.onSurfaceVariant
: Theme.of(context).colorScheme.onPrimaryContainer,
),
),
title: Text(
notification.title,
style: TextStyle(
fontWeight:
notification.isRead ? FontWeight.normal : FontWeight.bold,
),
),
subtitle: Text(notification.message),
trailing: Text(
notification.time,
style: Theme.of(context).textTheme.bodySmall,
),
);
},
separatorBuilder: (context, index) {
return const Divider(height: 1, indent: 72);
},
),
],
),
);
}
}
class _Notification {
final String title;
final String message;
final String time;
final bool isRead;
_Notification({
required this.title,
required this.message,
required this.time,
required this.isRead,
});
}
Mixed Sliver Layout with Translated Content
A CustomScrollView combining SliverList with SliverAppBar and SliverToBoxAdapter for a rich translated layout.
class MixedSliverLayout extends StatelessWidget {
const MixedSliverLayout({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
expandedHeight: 200,
pinned: true,
flexibleSpace: FlexibleSpaceBar(
title: Text(l10n.discoverTitle),
background: Container(
color: Theme.of(context).colorScheme.primaryContainer,
child: Center(
child: Icon(
Icons.explore,
size: 80,
color: Theme.of(context).colorScheme.onPrimaryContainer,
),
),
),
),
),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(16),
child: Text(
l10n.featuredSectionLabel,
style: Theme.of(context).textTheme.titleLarge,
),
),
),
SliverList.builder(
itemCount: 3,
itemBuilder: (context, index) {
return Card(
margin: const EdgeInsetsDirectional.fromSTEB(16, 0, 16, 8),
child: ListTile(
leading: const Icon(Icons.star),
title: Text('${l10n.featuredItemLabel} ${index + 1}'),
subtitle: Text(l10n.featuredItemDescription),
),
);
},
),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(16),
child: Text(
l10n.recentSectionLabel,
style: Theme.of(context).textTheme.titleLarge,
),
),
),
SliverList.separated(
itemCount: 10,
itemBuilder: (context, index) {
return ListTile(
leading: CircleAvatar(child: Text('${index + 1}')),
title: Text('${l10n.recentItemLabel} ${index + 1}'),
subtitle: Text(l10n.recentItemDescription),
);
},
separatorBuilder: (context, index) => const Divider(height: 1),
),
],
),
);
}
}
RTL Support and Bidirectional Layouts
SliverList respects the ambient text direction. List items align correctly in RTL layouts, and leading/trailing widgets swap positions automatically.
class BidirectionalSliverList extends StatelessWidget {
const BidirectionalSliverList({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final isRtl = Directionality.of(context) == TextDirection.rtl;
return Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
pinned: true,
title: Text(l10n.contactsTitle),
),
SliverList.builder(
itemCount: 10,
itemBuilder: (context, index) {
return ListTile(
leading: const CircleAvatar(child: Icon(Icons.person)),
title: Text('${l10n.contactLabel} ${index + 1}'),
subtitle: Text(l10n.contactSubtitle),
trailing: Icon(
isRtl ? Icons.chevron_left : Icons.chevron_right,
),
);
},
),
],
),
);
}
}
Testing SliverList Localization
import 'package:flutter/material.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 MaterialApp(
locale: locale,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: const LocalizedSliverListExample(),
);
}
testWidgets('SliverList renders localized items', (tester) async {
await tester.pumpWidget(buildTestWidget());
await tester.pumpAndSettle();
expect(find.byType(CustomScrollView), findsOneWidget);
});
testWidgets('SliverList works in RTL', (tester) async {
await tester.pumpWidget(buildTestWidget(locale: const Locale('ar')));
await tester.pumpAndSettle();
expect(tester.takeException(), isNull);
});
}
Best Practices
Use
SliverList.builderfor long translated lists to lazily build only visible items, keeping performance optimal regardless of list length.Use
SliverList.separatedto add dividers between translated list items without extra padding or manual separator logic.Combine SliverList with
SliverToBoxAdapterfor section headers that use translated text, creating structured scrollable layouts.Use
EdgeInsetsDirectionalfor all padding in list items and headers so spacing adapts correctly in RTL layouts.Swap directional icons (chevron_left/chevron_right) based on
Directionality.of(context)for list item trailing icons.Test with verbose translations to verify list items don't clip and text wraps correctly within each item's layout.
Conclusion
SliverList provides a sliver-based list widget for CustomScrollView layouts in Flutter. For multilingual apps, it handles translated list items with lazy building, supports separated lists with dividers, and integrates with other slivers for rich scrollable layouts. By combining SliverList with sectioned headers, mixed sliver layouts, and RTL-aware item alignment, you can build complex scrollable interfaces that display translated content efficiently across all supported languages.