Flutter SizedBox Localization: Fixed Sizing for Multilingual Layouts
SizedBox is a Flutter widget that forces its child to have a specific width and/or height. In multilingual applications, SizedBox provides essential spacing and sizing control that maintains visual consistency across languages with varying text lengths.
Understanding SizedBox in Localization Context
SizedBox creates fixed dimensions for its child or acts as a spacing widget when used without a child. For multilingual apps, this enables:
- Consistent spacing between elements regardless of text length
- Fixed-width containers for predictable layouts
- Vertical and horizontal gaps that work in RTL and LTR
- Empty space placeholders that respect directionality
Why SizedBox Matters for Multilingual Apps
Fixed sizing provides:
- Predictable layouts: Elements maintain position across languages
- Consistent spacing: Gaps remain uniform despite text variations
- Visual rhythm: Design patterns stay intact in all locales
- Simple implementation: Clear, explicit size declarations
Basic SizedBox Implementation
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class LocalizedSizedBoxExample extends StatelessWidget {
const LocalizedSizedBoxExample({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
l10n.welcomeTitle,
style: Theme.of(context).textTheme.headlineMedium,
),
const SizedBox(height: 16),
Text(
l10n.welcomeSubtitle,
style: Theme.of(context).textTheme.bodyLarge,
),
const SizedBox(height: 24),
FilledButton(
onPressed: () {},
child: Text(l10n.getStartedButton),
),
],
);
}
}
Spacing Patterns
Vertical Spacing System
class VerticalSpacing {
static const Widget xs = SizedBox(height: 4);
static const Widget sm = SizedBox(height: 8);
static const Widget md = SizedBox(height: 16);
static const Widget lg = SizedBox(height: 24);
static const Widget xl = SizedBox(height: 32);
static const Widget xxl = SizedBox(height: 48);
}
class LocalizedContentSection extends StatelessWidget {
const LocalizedContentSection({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
l10n.sectionTitle,
style: Theme.of(context).textTheme.titleLarge,
),
VerticalSpacing.sm,
Text(
l10n.sectionDescription,
style: Theme.of(context).textTheme.bodyMedium,
),
VerticalSpacing.lg,
Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: () {},
child: Text(l10n.secondaryAction),
),
),
const SizedBox(width: 12),
Expanded(
child: FilledButton(
onPressed: () {},
child: Text(l10n.primaryAction),
),
),
],
),
],
);
}
}
Horizontal Spacing System
class HorizontalSpacing {
static const Widget xs = SizedBox(width: 4);
static const Widget sm = SizedBox(width: 8);
static const Widget md = SizedBox(width: 16);
static const Widget lg = SizedBox(width: 24);
static const Widget xl = SizedBox(width: 32);
}
class LocalizedButtonRow extends StatelessWidget {
const LocalizedButtonRow({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: const Icon(Icons.share),
onPressed: () {},
tooltip: l10n.shareTooltip,
),
HorizontalSpacing.sm,
IconButton(
icon: const Icon(Icons.bookmark_border),
onPressed: () {},
tooltip: l10n.bookmarkTooltip,
),
HorizontalSpacing.sm,
IconButton(
icon: const Icon(Icons.more_vert),
onPressed: () {},
tooltip: l10n.moreOptionsTooltip,
),
],
);
}
}
Fixed-Width Components
Fixed-Width Label Column
class LocalizedFormRow extends StatelessWidget {
final String label;
final Widget field;
final double labelWidth;
const LocalizedFormRow({
super.key,
required this.label,
required this.field,
this.labelWidth = 120,
});
@override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
width: labelWidth,
child: Text(
label,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w500,
),
),
),
const SizedBox(width: 16),
Expanded(child: field),
],
);
}
}
// Usage
class FormExample extends StatelessWidget {
const FormExample({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Column(
children: [
LocalizedFormRow(
label: l10n.emailLabel,
field: const TextField(
decoration: InputDecoration(border: OutlineInputBorder()),
),
),
const SizedBox(height: 16),
LocalizedFormRow(
label: l10n.passwordLabel,
field: const TextField(
obscureText: true,
decoration: InputDecoration(border: OutlineInputBorder()),
),
),
],
);
}
}
Fixed-Size Icon Container
class LocalizedIconTile extends StatelessWidget {
final IconData icon;
final String title;
final String subtitle;
final VoidCallback? onTap;
const LocalizedIconTile({
super.key,
required this.icon,
required this.title,
required this.subtitle,
this.onTap,
});
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.all(12),
child: Row(
children: [
SizedBox(
width: 48,
height: 48,
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primaryContainer,
borderRadius: BorderRadius.circular(12),
),
child: Icon(
icon,
color: Theme.of(context).colorScheme.primary,
),
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: Theme.of(context).textTheme.titleSmall,
),
const SizedBox(height: 4),
Text(
subtitle,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.outline,
),
),
],
),
),
const Icon(Icons.chevron_right),
],
),
),
);
}
}
SizedBox.expand and SizedBox.shrink
Expand to Fill Available Space
class LocalizedExpandedContent extends StatelessWidget {
const LocalizedExpandedContent({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return SizedBox.expand(
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Theme.of(context).colorScheme.primaryContainer,
Theme.of(context).colorScheme.surface,
],
),
),
child: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.check_circle, size: 64),
const SizedBox(height: 24),
Text(
l10n.successTitle,
style: Theme.of(context).textTheme.headlineMedium,
textAlign: TextAlign.center,
),
const SizedBox(height: 12),
Text(
l10n.successMessage,
style: Theme.of(context).textTheme.bodyLarge,
textAlign: TextAlign.center,
),
],
),
),
),
);
}
}
Shrink for Zero-Size Placeholder
class ConditionalContent extends StatelessWidget {
final bool showContent;
final Widget content;
const ConditionalContent({
super.key,
required this.showContent,
required this.content,
});
@override
Widget build(BuildContext context) {
return showContent ? content : const SizedBox.shrink();
}
}
// Usage in localized context
class LocalizedConditionalBadge extends StatelessWidget {
final int count;
const LocalizedConditionalBadge({
super.key,
required this.count,
});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return count > 0
? Badge(
label: Text(l10n.notificationCount(count)),
child: const Icon(Icons.notifications),
)
: const SizedBox.shrink();
}
}
Language-Adaptive Sizing
Adaptive Spacing Based on Language
class AdaptiveSizedBox extends StatelessWidget {
final double baseWidth;
final double baseHeight;
final Widget? child;
const AdaptiveSizedBox({
super.key,
this.baseWidth = 0,
this.baseHeight = 0,
this.child,
});
@override
Widget build(BuildContext context) {
final locale = Localizations.localeOf(context);
final multiplier = _getMultiplier(locale);
return SizedBox(
width: baseWidth > 0 ? baseWidth * multiplier : null,
height: baseHeight > 0 ? baseHeight * multiplier : null,
child: child,
);
}
double _getMultiplier(Locale locale) {
switch (locale.languageCode) {
case 'de':
case 'ru':
case 'fi':
return 1.2; // Longer words need more space
case 'ja':
case 'zh':
case 'ko':
return 0.9; // More compact scripts
default:
return 1.0;
}
}
}
// Usage
class AdaptiveFormLayout extends StatelessWidget {
const AdaptiveFormLayout({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Column(
children: [
AdaptiveSizedBox(
baseWidth: 100,
child: Text(l10n.fieldLabel),
),
const AdaptiveSizedBox(baseHeight: 16),
TextField(
decoration: InputDecoration(
labelText: l10n.inputPlaceholder,
),
),
],
);
}
}
Grid and List Layouts
Fixed-Size Grid Items
class LocalizedGridView extends StatelessWidget {
const LocalizedGridView({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final items = [
(Icons.home, l10n.homeLabel),
(Icons.search, l10n.searchLabel),
(Icons.favorite, l10n.favoritesLabel),
(Icons.person, l10n.profileLabel),
];
return GridView.builder(
shrinkWrap: true,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 16,
crossAxisSpacing: 16,
childAspectRatio: 1.5,
),
itemCount: items.length,
itemBuilder: (context, index) {
final (icon, label) = items[index];
return Card(
child: InkWell(
onTap: () {},
borderRadius: BorderRadius.circular(12),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, size: 32),
const SizedBox(height: 8),
Text(
label,
style: Theme.of(context).textTheme.labelLarge,
textAlign: TextAlign.center,
),
],
),
),
);
},
);
}
}
Fixed-Height List Items
class LocalizedFixedHeightList extends StatelessWidget {
const LocalizedFixedHeightList({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return ListView(
children: [
SizedBox(
height: 72,
child: ListTile(
leading: const CircleAvatar(child: Icon(Icons.person)),
title: Text(l10n.accountSettings),
subtitle: Text(l10n.accountSettingsDesc),
trailing: const Icon(Icons.chevron_right),
onTap: () {},
),
),
const Divider(height: 1),
SizedBox(
height: 72,
child: ListTile(
leading: const CircleAvatar(child: Icon(Icons.notifications)),
title: Text(l10n.notificationSettings),
subtitle: Text(l10n.notificationSettingsDesc),
trailing: const Icon(Icons.chevron_right),
onTap: () {},
),
),
const Divider(height: 1),
SizedBox(
height: 72,
child: ListTile(
leading: const CircleAvatar(child: Icon(Icons.lock)),
title: Text(l10n.privacySettings),
subtitle: Text(l10n.privacySettingsDesc),
trailing: const Icon(Icons.chevron_right),
onTap: () {},
),
),
],
);
}
}
Dialog and Modal Sizing
Fixed-Size Dialog Content
class LocalizedSizedDialog extends StatelessWidget {
final String title;
final String message;
final List<Widget> actions;
const LocalizedSizedDialog({
super.key,
required this.title,
required this.message,
required this.actions,
});
@override
Widget build(BuildContext context) {
return Dialog(
child: SizedBox(
width: 320,
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 16),
Text(
message,
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 24),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: actions,
),
],
),
),
),
);
}
}
ARB File Structure
English (app_en.arb)
{
"@@locale": "en",
"welcomeTitle": "Welcome Back",
"welcomeSubtitle": "Sign in to continue to your account",
"getStartedButton": "Get Started",
"sectionTitle": "Featured Content",
"sectionDescription": "Discover the latest updates and featured items curated just for you.",
"primaryAction": "Continue",
"secondaryAction": "Learn More",
"shareTooltip": "Share",
"bookmarkTooltip": "Bookmark",
"moreOptionsTooltip": "More options",
"emailLabel": "Email",
"passwordLabel": "Password",
"successTitle": "Success!",
"successMessage": "Your action was completed successfully.",
"notificationCount": "{count, plural, =1{1 notification} other{{count} notifications}}",
"@notificationCount": {
"placeholders": {
"count": {"type": "int"}
}
},
"homeLabel": "Home",
"searchLabel": "Search",
"favoritesLabel": "Favorites",
"profileLabel": "Profile",
"accountSettings": "Account",
"accountSettingsDesc": "Manage your account settings",
"notificationSettings": "Notifications",
"notificationSettingsDesc": "Configure notification preferences",
"privacySettings": "Privacy",
"privacySettingsDesc": "Control your privacy settings"
}
German (app_de.arb)
{
"@@locale": "de",
"welcomeTitle": "Willkommen zurück",
"welcomeSubtitle": "Melden Sie sich an, um zu Ihrem Konto fortzufahren",
"getStartedButton": "Loslegen",
"sectionTitle": "Empfohlene Inhalte",
"sectionDescription": "Entdecken Sie die neuesten Updates und ausgewählte Artikel, die speziell für Sie zusammengestellt wurden.",
"primaryAction": "Fortfahren",
"secondaryAction": "Mehr erfahren",
"shareTooltip": "Teilen",
"bookmarkTooltip": "Lesezeichen",
"moreOptionsTooltip": "Weitere Optionen",
"emailLabel": "E-Mail",
"passwordLabel": "Passwort",
"successTitle": "Erfolg!",
"successMessage": "Ihre Aktion wurde erfolgreich abgeschlossen.",
"notificationCount": "{count, plural, =1{1 Benachrichtigung} other{{count} Benachrichtigungen}}",
"homeLabel": "Startseite",
"searchLabel": "Suchen",
"favoritesLabel": "Favoriten",
"profileLabel": "Profil",
"accountSettings": "Konto",
"accountSettingsDesc": "Kontoeinstellungen verwalten",
"notificationSettings": "Benachrichtigungen",
"notificationSettingsDesc": "Benachrichtigungseinstellungen konfigurieren",
"privacySettings": "Datenschutz",
"privacySettingsDesc": "Datenschutzeinstellungen steuern"
}
Arabic (app_ar.arb)
{
"@@locale": "ar",
"welcomeTitle": "مرحباً بعودتك",
"welcomeSubtitle": "سجل الدخول للمتابعة إلى حسابك",
"getStartedButton": "ابدأ الآن",
"sectionTitle": "المحتوى المميز",
"sectionDescription": "اكتشف آخر التحديثات والعناصر المميزة المختارة خصيصاً لك.",
"primaryAction": "متابعة",
"secondaryAction": "اعرف المزيد",
"shareTooltip": "مشاركة",
"bookmarkTooltip": "إشارة مرجعية",
"moreOptionsTooltip": "المزيد من الخيارات",
"emailLabel": "البريد الإلكتروني",
"passwordLabel": "كلمة المرور",
"successTitle": "نجاح!",
"successMessage": "تم إكمال الإجراء بنجاح.",
"notificationCount": "{count, plural, =0{لا توجد إشعارات} =1{إشعار واحد} =2{إشعاران} few{{count} إشعارات} many{{count} إشعاراً} other{{count} إشعار}}",
"homeLabel": "الرئيسية",
"searchLabel": "بحث",
"favoritesLabel": "المفضلة",
"profileLabel": "الملف الشخصي",
"accountSettings": "الحساب",
"accountSettingsDesc": "إدارة إعدادات الحساب",
"notificationSettings": "الإشعارات",
"notificationSettingsDesc": "تكوين تفضيلات الإشعارات",
"privacySettings": "الخصوصية",
"privacySettingsDesc": "التحكم في إعدادات الخصوصية"
}
Best Practices Summary
Do's
- Use SizedBox for consistent spacing between elements
- Create spacing constants for design system consistency
- Use SizedBox.shrink() for conditional empty content
- Combine with Expanded for flexible layouts
- Test spacing with different text lengths across languages
Don'ts
- Don't use fixed widths for text containers that need to adapt
- Don't forget that SizedBox respects RTL automatically
- Don't overuse fixed sizes when flex widgets are more appropriate
- Don't hardcode sizes without considering language variations
Conclusion
SizedBox is a fundamental building block for creating consistent, predictable layouts in multilingual Flutter applications. By establishing fixed dimensions for spacing and containers, you maintain visual rhythm and design integrity across all languages. Combine SizedBox with flexible widgets like Expanded and Flexible to create layouts that are both structured and adaptable.