← Back to Blog

Flutter SizedBox Localization: Fixed Sizing for Multilingual Layouts

fluttersizedboxsizingspacinglocalizationlayout

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

  1. Use SizedBox for consistent spacing between elements
  2. Create spacing constants for design system consistency
  3. Use SizedBox.shrink() for conditional empty content
  4. Combine with Expanded for flexible layouts
  5. Test spacing with different text lengths across languages

Don'ts

  1. Don't use fixed widths for text containers that need to adapt
  2. Don't forget that SizedBox respects RTL automatically
  3. Don't overuse fixed sizes when flex widgets are more appropriate
  4. 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.

Further Reading