← Back to Blog

Flutter AnimatedContainer Localization: Dynamic Layouts, Responsive Sizing, and Smooth Transitions

flutteranimatedcontaineranimationlayoutlocalizationaccessibility

Flutter AnimatedContainer Localization: Dynamic Layouts, Responsive Sizing, and Smooth Transitions

AnimatedContainer automatically animates between old and new values of its properties. Proper localization ensures that dynamic layouts, responsive sizing, and smooth transitions work seamlessly across languages and text directions. This guide covers comprehensive strategies for localizing AnimatedContainer widgets in Flutter.

Understanding AnimatedContainer Localization

AnimatedContainer widgets require localization for:

  • Dynamic sizing: Adapting container size for different text lengths
  • RTL alignment: Adjusting alignment for right-to-left languages
  • Responsive padding: Scaling padding based on content and locale
  • Color transitions: Theme-aware color changes with localized labels
  • Accessibility labels: Screen reader announcements for state changes
  • Animated text containers: Expanding/collapsing text sections

Basic AnimatedContainer with Localized Content

Start with a simple AnimatedContainer that adapts to localized content:

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

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

  @override
  State<LocalizedExpandableCard> createState() => _LocalizedExpandableCardState();
}

class _LocalizedExpandableCardState extends State<LocalizedExpandableCard> {
  bool _isExpanded = false;

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

    return GestureDetector(
      onTap: () => setState(() => _isExpanded = !_isExpanded),
      child: Semantics(
        label: _isExpanded ? l10n.collapseCard : l10n.expandCard,
        button: true,
        child: AnimatedContainer(
          duration: const Duration(milliseconds: 300),
          curve: Curves.easeInOut,
          width: double.infinity,
          padding: EdgeInsets.all(_isExpanded ? 24 : 16),
          margin: const EdgeInsets.all(16),
          decoration: BoxDecoration(
            color: _isExpanded
                ? Theme.of(context).colorScheme.primaryContainer
                : Theme.of(context).colorScheme.surface,
            borderRadius: BorderRadius.circular(_isExpanded ? 16 : 8),
            boxShadow: [
              BoxShadow(
                color: Colors.black.withOpacity(_isExpanded ? 0.15 : 0.05),
                blurRadius: _isExpanded ? 12 : 4,
                offset: Offset(0, _isExpanded ? 6 : 2),
              ),
            ],
          ),
          child: Column(
            crossAxisAlignment:
                isRtl ? CrossAxisAlignment.end : CrossAxisAlignment.start,
            children: [
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Text(
                    l10n.cardTitle,
                    style: Theme.of(context).textTheme.titleMedium?.copyWith(
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  AnimatedRotation(
                    turns: _isExpanded ? 0.5 : 0,
                    duration: const Duration(milliseconds: 300),
                    child: Icon(
                      Icons.expand_more,
                      semanticLabel: _isExpanded
                          ? l10n.collapseIcon
                          : l10n.expandIcon,
                    ),
                  ),
                ],
              ),
              AnimatedCrossFade(
                firstChild: const SizedBox.shrink(),
                secondChild: Padding(
                  padding: const EdgeInsets.only(top: 16),
                  child: Text(
                    l10n.cardDescription,
                    style: Theme.of(context).textTheme.bodyMedium,
                  ),
                ),
                crossFadeState: _isExpanded
                    ? CrossFadeState.showSecond
                    : CrossFadeState.showFirst,
                duration: const Duration(milliseconds: 300),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

ARB File Structure for AnimatedContainer

{
  "cardTitle": "Feature Details",
  "@cardTitle": {
    "description": "Title of the expandable card"
  },
  "cardDescription": "This is the detailed description that appears when the card is expanded. It provides additional information about the feature.",
  "@cardDescription": {
    "description": "Description shown when card is expanded"
  },
  "expandCard": "Tap to expand card",
  "@expandCard": {
    "description": "Accessibility label for collapsed card"
  },
  "collapseCard": "Tap to collapse card",
  "@collapseCard": {
    "description": "Accessibility label for expanded card"
  },
  "expandIcon": "Expand",
  "@expandIcon": {
    "description": "Accessibility label for expand icon"
  },
  "collapseIcon": "Collapse",
  "@collapseIcon": {
    "description": "Accessibility label for collapse icon"
  }
}

Responsive Size Animation Based on Locale

Create containers that adapt size based on text length:

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

class LocalizedAdaptiveContainer extends StatelessWidget {
  final bool isSelected;
  final VoidCallback onTap;

  const LocalizedAdaptiveContainer({
    super.key,
    required this.isSelected,
    required this.onTap,
  });

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

    // Calculate text width for adaptive sizing
    final textPainter = TextPainter(
      text: TextSpan(
        text: l10n.selectedLabel,
        style: Theme.of(context).textTheme.labelLarge,
      ),
      textDirection: isRtl ? TextDirection.rtl : TextDirection.ltr,
    )..layout();

    final minWidth = textPainter.width + 48; // Add padding

    return GestureDetector(
      onTap: onTap,
      child: Semantics(
        selected: isSelected,
        label: isSelected ? l10n.itemSelected : l10n.itemNotSelected,
        child: AnimatedContainer(
          duration: const Duration(milliseconds: 250),
          curve: Curves.easeOutCubic,
          constraints: BoxConstraints(
            minWidth: isSelected ? minWidth : 60,
            minHeight: 48,
          ),
          padding: EdgeInsets.symmetric(
            horizontal: isSelected ? 20 : 12,
            vertical: 12,
          ),
          decoration: BoxDecoration(
            color: isSelected
                ? Theme.of(context).colorScheme.primary
                : Theme.of(context).colorScheme.surfaceVariant,
            borderRadius: BorderRadius.circular(24),
            border: Border.all(
              color: isSelected
                  ? Theme.of(context).colorScheme.primary
                  : Theme.of(context).colorScheme.outline,
              width: isSelected ? 2 : 1,
            ),
          ),
          child: Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              Icon(
                isSelected ? Icons.check_circle : Icons.circle_outlined,
                size: 20,
                color: isSelected
                    ? Theme.of(context).colorScheme.onPrimary
                    : Theme.of(context).colorScheme.onSurfaceVariant,
              ),
              AnimatedSize(
                duration: const Duration(milliseconds: 250),
                curve: Curves.easeOutCubic,
                child: isSelected
                    ? Padding(
                        padding: EdgeInsetsDirectional.only(start: 8),
                        child: Text(
                          l10n.selectedLabel,
                          style: Theme.of(context).textTheme.labelLarge?.copyWith(
                            color: Theme.of(context).colorScheme.onPrimary,
                          ),
                        ),
                      )
                    : const SizedBox.shrink(),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Status Indicator with Animated Transitions

Build a status indicator that animates between states:

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

enum ConnectionStatus { connected, connecting, disconnected, error }

class LocalizedStatusIndicator extends StatelessWidget {
  final ConnectionStatus status;

  const LocalizedStatusIndicator({super.key, required this.status});

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

    return Semantics(
      liveRegion: true,
      label: statusConfig.accessibilityLabel,
      child: AnimatedContainer(
        duration: const Duration(milliseconds: 400),
        curve: Curves.easeInOut,
        padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
        decoration: BoxDecoration(
          color: statusConfig.backgroundColor,
          borderRadius: BorderRadius.circular(20),
          border: Border.all(
            color: statusConfig.borderColor,
            width: 1.5,
          ),
        ),
        child: Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            _buildStatusIcon(statusConfig),
            const SizedBox(width: 8),
            Text(
              statusConfig.label,
              style: Theme.of(context).textTheme.labelMedium?.copyWith(
                color: statusConfig.textColor,
                fontWeight: FontWeight.w600,
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildStatusIcon(_StatusConfig config) {
    if (status == ConnectionStatus.connecting) {
      return SizedBox(
        width: 16,
        height: 16,
        child: CircularProgressIndicator(
          strokeWidth: 2,
          valueColor: AlwaysStoppedAnimation(config.iconColor),
        ),
      );
    }

    return Icon(
      config.icon,
      size: 16,
      color: config.iconColor,
    );
  }

  _StatusConfig _getStatusConfig(AppLocalizations l10n, BuildContext context) {
    final theme = Theme.of(context);

    return switch (status) {
      ConnectionStatus.connected => _StatusConfig(
        label: l10n.statusConnected,
        accessibilityLabel: l10n.statusConnectedAccessibility,
        icon: Icons.check_circle,
        iconColor: Colors.green.shade700,
        textColor: Colors.green.shade800,
        backgroundColor: Colors.green.shade50,
        borderColor: Colors.green.shade200,
      ),
      ConnectionStatus.connecting => _StatusConfig(
        label: l10n.statusConnecting,
        accessibilityLabel: l10n.statusConnectingAccessibility,
        icon: Icons.sync,
        iconColor: Colors.blue.shade700,
        textColor: Colors.blue.shade800,
        backgroundColor: Colors.blue.shade50,
        borderColor: Colors.blue.shade200,
      ),
      ConnectionStatus.disconnected => _StatusConfig(
        label: l10n.statusDisconnected,
        accessibilityLabel: l10n.statusDisconnectedAccessibility,
        icon: Icons.cloud_off,
        iconColor: Colors.grey.shade600,
        textColor: Colors.grey.shade700,
        backgroundColor: Colors.grey.shade100,
        borderColor: Colors.grey.shade300,
      ),
      ConnectionStatus.error => _StatusConfig(
        label: l10n.statusError,
        accessibilityLabel: l10n.statusErrorAccessibility,
        icon: Icons.error,
        iconColor: Colors.red.shade700,
        textColor: Colors.red.shade800,
        backgroundColor: Colors.red.shade50,
        borderColor: Colors.red.shade200,
      ),
    };
  }
}

class _StatusConfig {
  final String label;
  final String accessibilityLabel;
  final IconData icon;
  final Color iconColor;
  final Color textColor;
  final Color backgroundColor;
  final Color borderColor;

  _StatusConfig({
    required this.label,
    required this.accessibilityLabel,
    required this.icon,
    required this.iconColor,
    required this.textColor,
    required this.backgroundColor,
    required this.borderColor,
  });
}

Animated Theme Switcher with Localized Labels

Create a theme toggle with smooth transitions:

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

class LocalizedThemeSwitcher extends StatefulWidget {
  final bool isDarkMode;
  final ValueChanged<bool> onChanged;

  const LocalizedThemeSwitcher({
    super.key,
    required this.isDarkMode,
    required this.onChanged,
  });

  @override
  State<LocalizedThemeSwitcher> createState() => _LocalizedThemeSwitcherState();
}

class _LocalizedThemeSwitcherState extends State<LocalizedThemeSwitcher> {
  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;
    final isRtl = Directionality.of(context) == TextDirection.rtl;

    return Semantics(
      label: widget.isDarkMode ? l10n.darkModeEnabled : l10n.lightModeEnabled,
      toggled: widget.isDarkMode,
      child: GestureDetector(
        onTap: () => widget.onChanged(!widget.isDarkMode),
        child: AnimatedContainer(
          duration: const Duration(milliseconds: 350),
          curve: Curves.easeInOutCubic,
          width: 160,
          height: 48,
          padding: const EdgeInsets.all(4),
          decoration: BoxDecoration(
            color: widget.isDarkMode
                ? const Color(0xFF1A1A2E)
                : const Color(0xFFF0F4FF),
            borderRadius: BorderRadius.circular(24),
            border: Border.all(
              color: widget.isDarkMode
                  ? const Color(0xFF3A3A5E)
                  : const Color(0xFFD0D8F0),
              width: 1.5,
            ),
          ),
          child: Stack(
            children: [
              // Sliding indicator
              AnimatedAlign(
                duration: const Duration(milliseconds: 350),
                curve: Curves.easeInOutCubic,
                alignment: widget.isDarkMode
                    ? (isRtl ? Alignment.centerLeft : Alignment.centerRight)
                    : (isRtl ? Alignment.centerRight : Alignment.centerLeft),
                child: Container(
                  width: 76,
                  height: 40,
                  decoration: BoxDecoration(
                    color: widget.isDarkMode
                        ? const Color(0xFF4A4A7E)
                        : Colors.white,
                    borderRadius: BorderRadius.circular(20),
                    boxShadow: [
                      BoxShadow(
                        color: Colors.black.withOpacity(0.1),
                        blurRadius: 4,
                        offset: const Offset(0, 2),
                      ),
                    ],
                  ),
                ),
              ),
              // Labels
              Row(
                children: [
                  Expanded(
                    child: Center(
                      child: Row(
                        mainAxisSize: MainAxisSize.min,
                        children: [
                          Icon(
                            Icons.light_mode,
                            size: 18,
                            color: widget.isDarkMode
                                ? Colors.grey.shade500
                                : Colors.orange.shade600,
                          ),
                          const SizedBox(width: 4),
                          Text(
                            l10n.lightMode,
                            style: TextStyle(
                              fontSize: 12,
                              fontWeight: FontWeight.w600,
                              color: widget.isDarkMode
                                  ? Colors.grey.shade500
                                  : Colors.grey.shade800,
                            ),
                          ),
                        ],
                      ),
                    ),
                  ),
                  Expanded(
                    child: Center(
                      child: Row(
                        mainAxisSize: MainAxisSize.min,
                        children: [
                          Icon(
                            Icons.dark_mode,
                            size: 18,
                            color: widget.isDarkMode
                                ? Colors.indigo.shade300
                                : Colors.grey.shade400,
                          ),
                          const SizedBox(width: 4),
                          Text(
                            l10n.darkMode,
                            style: TextStyle(
                              fontSize: 12,
                              fontWeight: FontWeight.w600,
                              color: widget.isDarkMode
                                  ? Colors.white
                                  : Colors.grey.shade400,
                            ),
                          ),
                        ],
                      ),
                    ),
                  ),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Animated Filter Chips with RTL Support

Build filter chips that animate selection:

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

class LocalizedAnimatedFilterChips extends StatefulWidget {
  final List<FilterOption> options;
  final Set<String> selectedIds;
  final ValueChanged<Set<String>> onChanged;

  const LocalizedAnimatedFilterChips({
    super.key,
    required this.options,
    required this.selectedIds,
    required this.onChanged,
  });

  @override
  State<LocalizedAnimatedFilterChips> createState() =>
      _LocalizedAnimatedFilterChipsState();
}

class _LocalizedAnimatedFilterChipsState
    extends State<LocalizedAnimatedFilterChips> {
  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;
    final isRtl = Directionality.of(context) == TextDirection.rtl;

    return Column(
      crossAxisAlignment:
          isRtl ? CrossAxisAlignment.end : CrossAxisAlignment.start,
      children: [
        Text(
          l10n.filterByCategory,
          style: Theme.of(context).textTheme.titleSmall,
        ),
        const SizedBox(height: 12),
        Wrap(
          spacing: 8,
          runSpacing: 8,
          textDirection: isRtl ? TextDirection.rtl : TextDirection.ltr,
          children: widget.options.map((option) {
            final isSelected = widget.selectedIds.contains(option.id);
            return _AnimatedFilterChip(
              label: option.label,
              icon: option.icon,
              isSelected: isSelected,
              onTap: () => _toggleSelection(option.id),
              selectedLabel: l10n.filterSelected(option.label),
              unselectedLabel: l10n.filterUnselected(option.label),
            );
          }).toList(),
        ),
        const SizedBox(height: 12),
        Text(
          l10n.selectedCount(widget.selectedIds.length),
          style: Theme.of(context).textTheme.bodySmall?.copyWith(
            color: Theme.of(context).colorScheme.outline,
          ),
        ),
      ],
    );
  }

  void _toggleSelection(String id) {
    final newSelection = Set<String>.from(widget.selectedIds);
    if (newSelection.contains(id)) {
      newSelection.remove(id);
    } else {
      newSelection.add(id);
    }
    widget.onChanged(newSelection);
  }
}

class _AnimatedFilterChip extends StatelessWidget {
  final String label;
  final IconData? icon;
  final bool isSelected;
  final VoidCallback onTap;
  final String selectedLabel;
  final String unselectedLabel;

  const _AnimatedFilterChip({
    required this.label,
    this.icon,
    required this.isSelected,
    required this.onTap,
    required this.selectedLabel,
    required this.unselectedLabel,
  });

  @override
  Widget build(BuildContext context) {
    return Semantics(
      selected: isSelected,
      label: isSelected ? selectedLabel : unselectedLabel,
      child: GestureDetector(
        onTap: onTap,
        child: AnimatedContainer(
          duration: const Duration(milliseconds: 200),
          curve: Curves.easeOutCubic,
          padding: EdgeInsets.symmetric(
            horizontal: isSelected ? 16 : 12,
            vertical: 8,
          ),
          decoration: BoxDecoration(
            color: isSelected
                ? Theme.of(context).colorScheme.primaryContainer
                : Theme.of(context).colorScheme.surface,
            borderRadius: BorderRadius.circular(20),
            border: Border.all(
              color: isSelected
                  ? Theme.of(context).colorScheme.primary
                  : Theme.of(context).colorScheme.outline.withOpacity(0.5),
              width: isSelected ? 2 : 1,
            ),
          ),
          child: Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              if (icon != null) ...[
                Icon(
                  icon,
                  size: 18,
                  color: isSelected
                      ? Theme.of(context).colorScheme.primary
                      : Theme.of(context).colorScheme.onSurface,
                ),
                const SizedBox(width: 6),
              ],
              Text(
                label,
                style: Theme.of(context).textTheme.labelLarge?.copyWith(
                  color: isSelected
                      ? Theme.of(context).colorScheme.primary
                      : Theme.of(context).colorScheme.onSurface,
                  fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
                ),
              ),
              AnimatedSize(
                duration: const Duration(milliseconds: 200),
                child: isSelected
                    ? Padding(
                        padding: const EdgeInsetsDirectional.only(start: 6),
                        child: Icon(
                          Icons.check,
                          size: 16,
                          color: Theme.of(context).colorScheme.primary,
                        ),
                      )
                    : const SizedBox.shrink(),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class FilterOption {
  final String id;
  final String label;
  final IconData? icon;

  FilterOption({
    required this.id,
    required this.label,
    this.icon,
  });
}

Animated Notification Banner

Create an animated notification that slides in:

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

class LocalizedNotificationBanner extends StatefulWidget {
  final NotificationType type;
  final String message;
  final VoidCallback? onDismiss;
  final VoidCallback? onAction;
  final String? actionLabel;

  const LocalizedNotificationBanner({
    super.key,
    required this.type,
    required this.message,
    this.onDismiss,
    this.onAction,
    this.actionLabel,
  });

  @override
  State<LocalizedNotificationBanner> createState() =>
      _LocalizedNotificationBannerState();
}

class _LocalizedNotificationBannerState
    extends State<LocalizedNotificationBanner> {
  bool _isVisible = false;

  @override
  void initState() {
    super.initState();
    Future.microtask(() {
      if (mounted) setState(() => _isVisible = true);
    });
  }

  void _dismiss() {
    setState(() => _isVisible = false);
    Future.delayed(const Duration(milliseconds: 300), () {
      widget.onDismiss?.call();
    });
  }

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;
    final isRtl = Directionality.of(context) == TextDirection.rtl;
    final config = _getTypeConfig(l10n);

    return AnimatedContainer(
      duration: const Duration(milliseconds: 300),
      curve: Curves.easeOutCubic,
      height: _isVisible ? null : 0,
      child: AnimatedOpacity(
        duration: const Duration(milliseconds: 200),
        opacity: _isVisible ? 1 : 0,
        child: Semantics(
          liveRegion: true,
          label: '${config.accessibilityPrefix}: ${widget.message}',
          child: Container(
            margin: const EdgeInsets.all(16),
            padding: const EdgeInsets.all(16),
            decoration: BoxDecoration(
              color: config.backgroundColor,
              borderRadius: BorderRadius.circular(12),
              border: Border.all(color: config.borderColor),
              boxShadow: [
                BoxShadow(
                  color: config.shadowColor,
                  blurRadius: 8,
                  offset: const Offset(0, 4),
                ),
              ],
            ),
            child: Row(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Icon(
                  config.icon,
                  color: config.iconColor,
                  size: 24,
                ),
                const SizedBox(width: 12),
                Expanded(
                  child: Column(
                    crossAxisAlignment: isRtl
                        ? CrossAxisAlignment.end
                        : CrossAxisAlignment.start,
                    children: [
                      Text(
                        config.title,
                        style: Theme.of(context).textTheme.titleSmall?.copyWith(
                          color: config.titleColor,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      const SizedBox(height: 4),
                      Text(
                        widget.message,
                        style: Theme.of(context).textTheme.bodyMedium?.copyWith(
                          color: config.messageColor,
                        ),
                      ),
                      if (widget.onAction != null && widget.actionLabel != null)
                        Padding(
                          padding: const EdgeInsets.only(top: 12),
                          child: TextButton(
                            onPressed: widget.onAction,
                            style: TextButton.styleFrom(
                              foregroundColor: config.actionColor,
                              padding: EdgeInsets.zero,
                              minimumSize: Size.zero,
                              tapTargetSize: MaterialTapTargetSize.shrinkWrap,
                            ),
                            child: Text(widget.actionLabel!),
                          ),
                        ),
                    ],
                  ),
                ),
                const SizedBox(width: 8),
                IconButton(
                  onPressed: _dismiss,
                  icon: const Icon(Icons.close),
                  iconSize: 20,
                  padding: EdgeInsets.zero,
                  constraints: const BoxConstraints(),
                  tooltip: l10n.dismissNotification,
                  color: config.dismissColor,
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }

  _NotificationConfig _getTypeConfig(AppLocalizations l10n) {
    return switch (widget.type) {
      NotificationType.success => _NotificationConfig(
        title: l10n.notificationSuccess,
        accessibilityPrefix: l10n.successNotification,
        icon: Icons.check_circle,
        iconColor: Colors.green.shade600,
        titleColor: Colors.green.shade800,
        messageColor: Colors.green.shade700,
        backgroundColor: Colors.green.shade50,
        borderColor: Colors.green.shade200,
        shadowColor: Colors.green.withOpacity(0.1),
        actionColor: Colors.green.shade700,
        dismissColor: Colors.green.shade400,
      ),
      NotificationType.error => _NotificationConfig(
        title: l10n.notificationError,
        accessibilityPrefix: l10n.errorNotification,
        icon: Icons.error,
        iconColor: Colors.red.shade600,
        titleColor: Colors.red.shade800,
        messageColor: Colors.red.shade700,
        backgroundColor: Colors.red.shade50,
        borderColor: Colors.red.shade200,
        shadowColor: Colors.red.withOpacity(0.1),
        actionColor: Colors.red.shade700,
        dismissColor: Colors.red.shade400,
      ),
      NotificationType.warning => _NotificationConfig(
        title: l10n.notificationWarning,
        accessibilityPrefix: l10n.warningNotification,
        icon: Icons.warning,
        iconColor: Colors.orange.shade600,
        titleColor: Colors.orange.shade800,
        messageColor: Colors.orange.shade700,
        backgroundColor: Colors.orange.shade50,
        borderColor: Colors.orange.shade200,
        shadowColor: Colors.orange.withOpacity(0.1),
        actionColor: Colors.orange.shade700,
        dismissColor: Colors.orange.shade400,
      ),
      NotificationType.info => _NotificationConfig(
        title: l10n.notificationInfo,
        accessibilityPrefix: l10n.infoNotification,
        icon: Icons.info,
        iconColor: Colors.blue.shade600,
        titleColor: Colors.blue.shade800,
        messageColor: Colors.blue.shade700,
        backgroundColor: Colors.blue.shade50,
        borderColor: Colors.blue.shade200,
        shadowColor: Colors.blue.withOpacity(0.1),
        actionColor: Colors.blue.shade700,
        dismissColor: Colors.blue.shade400,
      ),
    };
  }
}

enum NotificationType { success, error, warning, info }

class _NotificationConfig {
  final String title;
  final String accessibilityPrefix;
  final IconData icon;
  final Color iconColor;
  final Color titleColor;
  final Color messageColor;
  final Color backgroundColor;
  final Color borderColor;
  final Color shadowColor;
  final Color actionColor;
  final Color dismissColor;

  _NotificationConfig({
    required this.title,
    required this.accessibilityPrefix,
    required this.icon,
    required this.iconColor,
    required this.titleColor,
    required this.messageColor,
    required this.backgroundColor,
    required this.borderColor,
    required this.shadowColor,
    required this.actionColor,
    required this.dismissColor,
  });
}

Complete ARB File for AnimatedContainer

{
  "@@locale": "en",

  "cardTitle": "Feature Details",
  "@cardTitle": {
    "description": "Title of the expandable card"
  },
  "cardDescription": "This is the detailed description that appears when the card is expanded. It provides additional information about the feature.",
  "@cardDescription": {
    "description": "Description shown when card is expanded"
  },
  "expandCard": "Tap to expand card",
  "@expandCard": {
    "description": "Accessibility label for collapsed card"
  },
  "collapseCard": "Tap to collapse card",
  "@collapseCard": {
    "description": "Accessibility label for expanded card"
  },
  "expandIcon": "Expand",
  "@expandIcon": {
    "description": "Accessibility label for expand icon"
  },
  "collapseIcon": "Collapse",
  "@collapseIcon": {
    "description": "Accessibility label for collapse icon"
  },

  "selectedLabel": "Selected",
  "@selectedLabel": {
    "description": "Label shown when item is selected"
  },
  "itemSelected": "Item selected",
  "@itemSelected": {
    "description": "Accessibility announcement when item is selected"
  },
  "itemNotSelected": "Item not selected",
  "@itemNotSelected": {
    "description": "Accessibility announcement when item is not selected"
  },

  "statusConnected": "Connected",
  "@statusConnected": {
    "description": "Connected status label"
  },
  "statusConnectedAccessibility": "Status: Connected to server",
  "@statusConnectedAccessibility": {
    "description": "Connected status accessibility label"
  },
  "statusConnecting": "Connecting...",
  "@statusConnecting": {
    "description": "Connecting status label"
  },
  "statusConnectingAccessibility": "Status: Connecting to server",
  "@statusConnectingAccessibility": {
    "description": "Connecting status accessibility label"
  },
  "statusDisconnected": "Disconnected",
  "@statusDisconnected": {
    "description": "Disconnected status label"
  },
  "statusDisconnectedAccessibility": "Status: Disconnected from server",
  "@statusDisconnectedAccessibility": {
    "description": "Disconnected status accessibility label"
  },
  "statusError": "Error",
  "@statusError": {
    "description": "Error status label"
  },
  "statusErrorAccessibility": "Status: Connection error",
  "@statusErrorAccessibility": {
    "description": "Error status accessibility label"
  },

  "lightMode": "Light",
  "@lightMode": {
    "description": "Light mode label"
  },
  "darkMode": "Dark",
  "@darkMode": {
    "description": "Dark mode label"
  },
  "lightModeEnabled": "Light mode enabled. Tap to switch to dark mode.",
  "@lightModeEnabled": {
    "description": "Light mode accessibility label"
  },
  "darkModeEnabled": "Dark mode enabled. Tap to switch to light mode.",
  "@darkModeEnabled": {
    "description": "Dark mode accessibility label"
  },

  "filterByCategory": "Filter by category",
  "@filterByCategory": {
    "description": "Filter section title"
  },
  "filterSelected": "{filter} filter selected",
  "@filterSelected": {
    "description": "Filter selected accessibility label",
    "placeholders": {
      "filter": {"type": "String"}
    }
  },
  "filterUnselected": "{filter} filter not selected",
  "@filterUnselected": {
    "description": "Filter unselected accessibility label",
    "placeholders": {
      "filter": {"type": "String"}
    }
  },
  "selectedCount": "{count} {count, plural, =0{filters} =1{filter} other{filters}} selected",
  "@selectedCount": {
    "description": "Selected filters count",
    "placeholders": {
      "count": {"type": "int"}
    }
  },

  "dismissNotification": "Dismiss notification",
  "@dismissNotification": {
    "description": "Dismiss button tooltip"
  },
  "notificationSuccess": "Success",
  "@notificationSuccess": {
    "description": "Success notification title"
  },
  "notificationError": "Error",
  "@notificationError": {
    "description": "Error notification title"
  },
  "notificationWarning": "Warning",
  "@notificationWarning": {
    "description": "Warning notification title"
  },
  "notificationInfo": "Information",
  "@notificationInfo": {
    "description": "Info notification title"
  },
  "successNotification": "Success notification",
  "@successNotification": {
    "description": "Success notification accessibility prefix"
  },
  "errorNotification": "Error notification",
  "@errorNotification": {
    "description": "Error notification accessibility prefix"
  },
  "warningNotification": "Warning notification",
  "@warningNotification": {
    "description": "Warning notification accessibility prefix"
  },
  "infoNotification": "Information notification",
  "@infoNotification": {
    "description": "Info notification accessibility prefix"
  }
}

Best Practices Summary

  1. Use AnimatedContainer for implicit animations: Let Flutter handle the animation when properties change
  2. Support RTL layouts: Adjust alignments and directions for right-to-left languages
  3. Provide accessibility labels: Announce state changes for screen readers
  4. Calculate text-based sizing: Measure localized text to determine container dimensions
  5. Use semantic widgets: Wrap animated containers in Semantics for accessibility
  6. Choose appropriate curves: Use easeInOut for smooth bidirectional animations
  7. Handle text length variations: Languages have different text lengths—design containers to adapt
  8. Animate decoration changes: Smoothly transition colors, borders, and shadows
  9. Use liveRegion for dynamic updates: Announce changes to screen readers
  10. Test with multiple locales: Verify animations work with different text lengths and directions

Conclusion

Proper AnimatedContainer localization ensures smooth, accessible animations across all languages. By handling RTL layouts, adapting to text lengths, and providing comprehensive accessibility labels, you create polished animated interfaces that feel native to users worldwide. The patterns shown here—expandable cards, status indicators, theme switchers, and filter chips—can be adapted to any Flutter application requiring animated localized containers.

Remember to test your animated containers with different locales to verify that sizing, alignment, and accessibility work correctly for all your supported languages.