← Back to Blog

Flutter AnimatedPadding Localization: Dynamic Spacing, Content Adaptation, and Responsive Layouts

flutteranimatedpaddinganimationspacinglocalizationaccessibility

Flutter AnimatedPadding Localization: Dynamic Spacing, Content Adaptation, and Responsive Layouts

AnimatedPadding smoothly animates changes to padding values over time. Proper localization ensures that dynamic spacing, content-aware padding, and responsive layouts adapt correctly across languages with varying text lengths and text directions. This guide covers comprehensive strategies for localizing AnimatedPadding widgets in Flutter.

Understanding AnimatedPadding Localization

AnimatedPadding widgets require localization for:

  • RTL-aware spacing: Using directional padding (start/end) for right-to-left languages
  • Content-based padding: Adjusting padding based on localized content size
  • Responsive layouts: Scaling spacing for different text lengths across locales
  • Focus states: Animating padding changes for selected/focused elements
  • Accessibility spacing: Ensuring touch targets remain adequate in all languages
  • Expansion animations: Smooth reveal of localized content

Basic AnimatedPadding with RTL Support

Start with a simple AnimatedPadding that respects text direction:

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

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

  @override
  State<LocalizedExpandingCard> createState() => _LocalizedExpandingCardState();
}

class _LocalizedExpandingCardState extends State<LocalizedExpandingCard> {
  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(
        button: true,
        label: _isExpanded ? l10n.tapToCollapse : l10n.tapToExpand,
        child: Card(
          child: AnimatedPadding(
            duration: const Duration(milliseconds: 300),
            curve: Curves.easeInOut,
            padding: EdgeInsetsDirectional.only(
              start: _isExpanded ? 24 : 16,
              end: _isExpanded ? 24 : 16,
              top: _isExpanded ? 20 : 12,
              bottom: _isExpanded ? 20 : 12,
            ),
            child: Column(
              crossAxisAlignment: isRtl
                  ? CrossAxisAlignment.end
                  : CrossAxisAlignment.start,
              children: [
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    Expanded(
                      child: 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: 12),
                    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 AnimatedPadding

{
  "tapToExpand": "Tap to expand card",
  "@tapToExpand": {
    "description": "Accessibility label for collapsed card"
  },
  "tapToCollapse": "Tap to collapse card",
  "@tapToCollapse": {
    "description": "Accessibility label for expanded card"
  },
  "cardTitle": "Feature Overview",
  "@cardTitle": {
    "description": "Title of the expandable card"
  },
  "cardDescription": "This section contains detailed information about the feature. When expanded, additional content is revealed with smooth padding animation.",
  "@cardDescription": {
    "description": "Description shown when card is expanded"
  },
  "expandIcon": "Expand",
  "@expandIcon": {
    "description": "Accessibility label for expand icon"
  },
  "collapseIcon": "Collapse",
  "@collapseIcon": {
    "description": "Accessibility label for collapse icon"
  }
}

Selectable List Item with Animated Padding

Create list items that expand when selected:

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

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

  @override
  State<LocalizedSelectableList> createState() => _LocalizedSelectableListState();
}

class _LocalizedSelectableListState extends State<LocalizedSelectableList> {
  int? _selectedIndex;

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

    final items = [
      _ListItemData(l10n.itemInbox, l10n.itemInboxSubtitle, Icons.inbox),
      _ListItemData(l10n.itemSent, l10n.itemSentSubtitle, Icons.send),
      _ListItemData(l10n.itemDrafts, l10n.itemDraftsSubtitle, Icons.drafts),
      _ListItemData(l10n.itemTrash, l10n.itemTrashSubtitle, Icons.delete),
    ];

    return ListView.builder(
      itemCount: items.length,
      itemBuilder: (context, index) {
        final isSelected = _selectedIndex == index;
        final item = items[index];

        return _LocalizedSelectableItem(
          title: item.title,
          subtitle: item.subtitle,
          icon: item.icon,
          isSelected: isSelected,
          onTap: () => setState(() {
            _selectedIndex = _selectedIndex == index ? null : index;
          }),
          selectedLabel: l10n.itemSelected(item.title),
          unselectedLabel: l10n.itemUnselected(item.title),
        );
      },
    );
  }
}

class _ListItemData {
  final String title;
  final String subtitle;
  final IconData icon;

  _ListItemData(this.title, this.subtitle, this.icon);
}

class _LocalizedSelectableItem extends StatelessWidget {
  final String title;
  final String subtitle;
  final IconData icon;
  final bool isSelected;
  final VoidCallback onTap;
  final String selectedLabel;
  final String unselectedLabel;

  const _LocalizedSelectableItem({
    required this.title,
    required this.subtitle,
    required this.icon,
    required this.isSelected,
    required this.onTap,
    required this.selectedLabel,
    required this.unselectedLabel,
  });

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

    return Semantics(
      selected: isSelected,
      label: isSelected ? selectedLabel : unselectedLabel,
      child: GestureDetector(
        onTap: onTap,
        child: AnimatedContainer(
          duration: const Duration(milliseconds: 250),
          curve: Curves.easeOutCubic,
          margin: EdgeInsets.symmetric(
            horizontal: isSelected ? 8 : 16,
            vertical: 4,
          ),
          decoration: BoxDecoration(
            color: isSelected
                ? Theme.of(context).colorScheme.primaryContainer
                : Theme.of(context).colorScheme.surface,
            borderRadius: BorderRadius.circular(isSelected ? 16 : 8),
            border: Border.all(
              color: isSelected
                  ? Theme.of(context).colorScheme.primary
                  : Theme.of(context).colorScheme.outline.withOpacity(0.2),
              width: isSelected ? 2 : 1,
            ),
            boxShadow: isSelected
                ? [
                    BoxShadow(
                      color: Theme.of(context)
                          .colorScheme
                          .primary
                          .withOpacity(0.2),
                      blurRadius: 8,
                      offset: const Offset(0, 2),
                    ),
                  ]
                : null,
          ),
          child: AnimatedPadding(
            duration: const Duration(milliseconds: 250),
            curve: Curves.easeOutCubic,
            padding: EdgeInsetsDirectional.only(
              start: isSelected ? 20 : 16,
              end: isSelected ? 20 : 16,
              top: isSelected ? 16 : 12,
              bottom: isSelected ? 16 : 12,
            ),
            child: Row(
              children: [
                AnimatedContainer(
                  duration: const Duration(milliseconds: 250),
                  padding: EdgeInsets.all(isSelected ? 12 : 8),
                  decoration: BoxDecoration(
                    color: isSelected
                        ? Theme.of(context).colorScheme.primary
                        : Theme.of(context).colorScheme.surfaceVariant,
                    borderRadius: BorderRadius.circular(isSelected ? 12 : 8),
                  ),
                  child: Icon(
                    icon,
                    size: isSelected ? 24 : 20,
                    color: isSelected
                        ? Theme.of(context).colorScheme.onPrimary
                        : Theme.of(context).colorScheme.onSurfaceVariant,
                  ),
                ),
                SizedBox(width: isRtl ? 0 : 16, height: isRtl ? 16 : 0),
                Expanded(
                  child: Column(
                    crossAxisAlignment: isRtl
                        ? CrossAxisAlignment.end
                        : CrossAxisAlignment.start,
                    children: [
                      Text(
                        title,
                        style: Theme.of(context).textTheme.titleMedium?.copyWith(
                          fontWeight:
                              isSelected ? FontWeight.bold : FontWeight.normal,
                          color: isSelected
                              ? Theme.of(context).colorScheme.onPrimaryContainer
                              : null,
                        ),
                      ),
                      if (isSelected) ...[
                        const SizedBox(height: 4),
                        Text(
                          subtitle,
                          style:
                              Theme.of(context).textTheme.bodySmall?.copyWith(
                            color: Theme.of(context)
                                .colorScheme
                                .onPrimaryContainer
                                .withOpacity(0.8),
                          ),
                        ),
                      ],
                    ],
                  ),
                ),
                AnimatedRotation(
                  turns: isSelected ? (isRtl ? -0.25 : 0.25) : 0,
                  duration: const Duration(milliseconds: 250),
                  child: Icon(
                    isRtl ? Icons.chevron_left : Icons.chevron_right,
                    color: isSelected
                        ? Theme.of(context).colorScheme.primary
                        : Theme.of(context).colorScheme.outline,
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Text Field with Animated Focus Padding

Create a text field that expands padding on focus:

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

class LocalizedAnimatedTextField extends StatefulWidget {
  final String label;
  final String hint;
  final TextEditingController? controller;
  final String? Function(String?)? validator;

  const LocalizedAnimatedTextField({
    super.key,
    required this.label,
    required this.hint,
    this.controller,
    this.validator,
  });

  @override
  State<LocalizedAnimatedTextField> createState() =>
      _LocalizedAnimatedTextFieldState();
}

class _LocalizedAnimatedTextFieldState
    extends State<LocalizedAnimatedTextField> {
  final FocusNode _focusNode = FocusNode();
  bool _isFocused = false;
  bool _hasError = false;

  @override
  void initState() {
    super.initState();
    _focusNode.addListener(_handleFocusChange);
  }

  void _handleFocusChange() {
    setState(() => _isFocused = _focusNode.hasFocus);
  }

  @override
  void dispose() {
    _focusNode.removeListener(_handleFocusChange);
    _focusNode.dispose();
    super.dispose();
  }

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

    return AnimatedPadding(
      duration: const Duration(milliseconds: 200),
      curve: Curves.easeOutCubic,
      padding: EdgeInsets.symmetric(
        horizontal: _isFocused ? 8 : 16,
        vertical: 8,
      ),
      child: AnimatedContainer(
        duration: const Duration(milliseconds: 200),
        curve: Curves.easeOutCubic,
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(_isFocused ? 16 : 12),
          border: Border.all(
            color: _hasError
                ? Theme.of(context).colorScheme.error
                : _isFocused
                    ? Theme.of(context).colorScheme.primary
                    : Theme.of(context).colorScheme.outline.withOpacity(0.3),
            width: _isFocused ? 2 : 1,
          ),
          boxShadow: _isFocused
              ? [
                  BoxShadow(
                    color: (_hasError
                            ? Theme.of(context).colorScheme.error
                            : Theme.of(context).colorScheme.primary)
                        .withOpacity(0.15),
                    blurRadius: 12,
                    offset: const Offset(0, 4),
                  ),
                ]
              : null,
        ),
        child: AnimatedPadding(
          duration: const Duration(milliseconds: 200),
          padding: EdgeInsetsDirectional.only(
            start: _isFocused ? 20 : 16,
            end: _isFocused ? 20 : 16,
            top: 4,
            bottom: 4,
          ),
          child: TextFormField(
            focusNode: _focusNode,
            controller: widget.controller,
            textDirection: isRtl ? TextDirection.rtl : TextDirection.ltr,
            decoration: InputDecoration(
              labelText: widget.label,
              hintText: widget.hint,
              border: InputBorder.none,
              errorStyle: const TextStyle(height: 0, fontSize: 0),
            ),
            validator: (value) {
              final error = widget.validator?.call(value);
              WidgetsBinding.instance.addPostFrameCallback((_) {
                if (mounted) {
                  setState(() => _hasError = error != null);
                }
              });
              return error;
            },
          ),
        ),
      ),
    );
  }
}

// Form example
class LocalizedAnimatedForm extends StatefulWidget {
  const LocalizedAnimatedForm({super.key});

  @override
  State<LocalizedAnimatedForm> createState() => _LocalizedAnimatedFormState();
}

class _LocalizedAnimatedFormState extends State<LocalizedAnimatedForm> {
  final _formKey = GlobalKey<FormState>();

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

    return Form(
      key: _formKey,
      child: Column(
        children: [
          LocalizedAnimatedTextField(
            label: l10n.emailLabel,
            hint: l10n.emailHint,
            validator: (value) {
              if (value == null || value.isEmpty) {
                return l10n.emailRequired;
              }
              if (!value.contains('@')) {
                return l10n.emailInvalid;
              }
              return null;
            },
          ),
          LocalizedAnimatedTextField(
            label: l10n.passwordLabel,
            hint: l10n.passwordHint,
            validator: (value) {
              if (value == null || value.isEmpty) {
                return l10n.passwordRequired;
              }
              if (value.length < 8) {
                return l10n.passwordTooShort;
              }
              return null;
            },
          ),
          const SizedBox(height: 16),
          ElevatedButton(
            onPressed: () {
              _formKey.currentState?.validate();
            },
            child: Text(l10n.submitButton),
          ),
        ],
      ),
    );
  }
}

Notification Card with Importance-Based Padding

Create notifications with padding that reflects importance:

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

enum NotificationImportance { low, medium, high, urgent }

class LocalizedNotificationCard extends StatelessWidget {
  final String title;
  final String message;
  final NotificationImportance importance;
  final VoidCallback? onDismiss;
  final VoidCallback? onTap;

  const LocalizedNotificationCard({
    super.key,
    required this.title,
    required this.message,
    required this.importance,
    this.onDismiss,
    this.onTap,
  });

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

    return Semantics(
      label: l10n.notificationAccessibility(
        config.importanceLabel,
        title,
      ),
      child: Dismissible(
        key: UniqueKey(),
        direction: DismissDirection.horizontal,
        onDismissed: (_) => onDismiss?.call(),
        background: Container(
          alignment: isRtl ? Alignment.centerRight : Alignment.centerLeft,
          padding: const EdgeInsets.symmetric(horizontal: 20),
          color: Theme.of(context).colorScheme.error,
          child: Icon(
            Icons.delete,
            color: Theme.of(context).colorScheme.onError,
          ),
        ),
        child: GestureDetector(
          onTap: onTap,
          child: AnimatedPadding(
            duration: const Duration(milliseconds: 300),
            curve: Curves.easeOutCubic,
            padding: EdgeInsetsDirectional.only(
              start: config.horizontalPadding,
              end: config.horizontalPadding,
              top: config.verticalPadding,
              bottom: config.verticalPadding,
            ),
            child: Container(
              decoration: BoxDecoration(
                color: config.backgroundColor,
                borderRadius: BorderRadius.circular(config.borderRadius),
                border: Border.all(
                  color: config.borderColor,
                  width: config.borderWidth,
                ),
                boxShadow: [
                  BoxShadow(
                    color: config.shadowColor,
                    blurRadius: config.shadowBlur,
                    offset: Offset(0, config.shadowOffset),
                  ),
                ],
              ),
              child: Padding(
                padding: EdgeInsets.all(config.contentPadding),
                child: Row(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Container(
                      padding: EdgeInsets.all(config.iconPadding),
                      decoration: BoxDecoration(
                        color: config.iconBackgroundColor,
                        borderRadius: BorderRadius.circular(8),
                      ),
                      child: Icon(
                        config.icon,
                        color: config.iconColor,
                        size: config.iconSize,
                      ),
                    ),
                    const SizedBox(width: 12),
                    Expanded(
                      child: Column(
                        crossAxisAlignment: isRtl
                            ? CrossAxisAlignment.end
                            : CrossAxisAlignment.start,
                        children: [
                          Row(
                            mainAxisAlignment: MainAxisAlignment.spaceBetween,
                            children: [
                              Expanded(
                                child: Text(
                                  title,
                                  style: Theme.of(context)
                                      .textTheme
                                      .titleSmall
                                      ?.copyWith(
                                    fontWeight: FontWeight.bold,
                                    color: config.titleColor,
                                  ),
                                ),
                              ),
                              Container(
                                padding: const EdgeInsets.symmetric(
                                  horizontal: 8,
                                  vertical: 2,
                                ),
                                decoration: BoxDecoration(
                                  color: config.badgeColor,
                                  borderRadius: BorderRadius.circular(12),
                                ),
                                child: Text(
                                  config.importanceLabel,
                                  style: Theme.of(context)
                                      .textTheme
                                      .labelSmall
                                      ?.copyWith(
                                    color: config.badgeTextColor,
                                    fontWeight: FontWeight.bold,
                                  ),
                                ),
                              ),
                            ],
                          ),
                          const SizedBox(height: 4),
                          Text(
                            message,
                            style:
                                Theme.of(context).textTheme.bodyMedium?.copyWith(
                              color: config.messageColor,
                            ),
                          ),
                        ],
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }

  _ImportanceConfig _getImportanceConfig(
    BuildContext context,
    AppLocalizations l10n,
  ) {
    return switch (importance) {
      NotificationImportance.low => _ImportanceConfig(
        importanceLabel: l10n.importanceLow,
        horizontalPadding: 16,
        verticalPadding: 8,
        contentPadding: 12,
        borderRadius: 8,
        borderWidth: 1,
        borderColor: Colors.grey.shade300,
        backgroundColor: Colors.grey.shade50,
        shadowColor: Colors.black.withOpacity(0.05),
        shadowBlur: 4,
        shadowOffset: 2,
        icon: Icons.info_outline,
        iconColor: Colors.grey.shade600,
        iconBackgroundColor: Colors.grey.shade200,
        iconSize: 20,
        iconPadding: 8,
        titleColor: Colors.grey.shade800,
        messageColor: Colors.grey.shade600,
        badgeColor: Colors.grey.shade200,
        badgeTextColor: Colors.grey.shade700,
      ),
      NotificationImportance.medium => _ImportanceConfig(
        importanceLabel: l10n.importanceMedium,
        horizontalPadding: 14,
        verticalPadding: 10,
        contentPadding: 14,
        borderRadius: 10,
        borderWidth: 1,
        borderColor: Colors.blue.shade200,
        backgroundColor: Colors.blue.shade50,
        shadowColor: Colors.blue.withOpacity(0.1),
        shadowBlur: 6,
        shadowOffset: 3,
        icon: Icons.notifications_outlined,
        iconColor: Colors.blue.shade600,
        iconBackgroundColor: Colors.blue.shade100,
        iconSize: 22,
        iconPadding: 10,
        titleColor: Colors.blue.shade800,
        messageColor: Colors.blue.shade700,
        badgeColor: Colors.blue.shade200,
        badgeTextColor: Colors.blue.shade800,
      ),
      NotificationImportance.high => _ImportanceConfig(
        importanceLabel: l10n.importanceHigh,
        horizontalPadding: 12,
        verticalPadding: 12,
        contentPadding: 16,
        borderRadius: 12,
        borderWidth: 2,
        borderColor: Colors.orange.shade300,
        backgroundColor: Colors.orange.shade50,
        shadowColor: Colors.orange.withOpacity(0.15),
        shadowBlur: 8,
        shadowOffset: 4,
        icon: Icons.warning_amber,
        iconColor: Colors.orange.shade700,
        iconBackgroundColor: Colors.orange.shade100,
        iconSize: 24,
        iconPadding: 10,
        titleColor: Colors.orange.shade900,
        messageColor: Colors.orange.shade800,
        badgeColor: Colors.orange.shade300,
        badgeTextColor: Colors.orange.shade900,
      ),
      NotificationImportance.urgent => _ImportanceConfig(
        importanceLabel: l10n.importanceUrgent,
        horizontalPadding: 8,
        verticalPadding: 14,
        contentPadding: 18,
        borderRadius: 14,
        borderWidth: 2,
        borderColor: Colors.red.shade400,
        backgroundColor: Colors.red.shade50,
        shadowColor: Colors.red.withOpacity(0.2),
        shadowBlur: 12,
        shadowOffset: 6,
        icon: Icons.error,
        iconColor: Colors.red.shade700,
        iconBackgroundColor: Colors.red.shade100,
        iconSize: 26,
        iconPadding: 12,
        titleColor: Colors.red.shade900,
        messageColor: Colors.red.shade800,
        badgeColor: Colors.red.shade400,
        badgeTextColor: Colors.white,
      ),
    };
  }
}

class _ImportanceConfig {
  final String importanceLabel;
  final double horizontalPadding;
  final double verticalPadding;
  final double contentPadding;
  final double borderRadius;
  final double borderWidth;
  final Color borderColor;
  final Color backgroundColor;
  final Color shadowColor;
  final double shadowBlur;
  final double shadowOffset;
  final IconData icon;
  final Color iconColor;
  final Color iconBackgroundColor;
  final double iconSize;
  final double iconPadding;
  final Color titleColor;
  final Color messageColor;
  final Color badgeColor;
  final Color badgeTextColor;

  _ImportanceConfig({
    required this.importanceLabel,
    required this.horizontalPadding,
    required this.verticalPadding,
    required this.contentPadding,
    required this.borderRadius,
    required this.borderWidth,
    required this.borderColor,
    required this.backgroundColor,
    required this.shadowColor,
    required this.shadowBlur,
    required this.shadowOffset,
    required this.icon,
    required this.iconColor,
    required this.iconBackgroundColor,
    required this.iconSize,
    required this.iconPadding,
    required this.titleColor,
    required this.messageColor,
    required this.badgeColor,
    required this.badgeTextColor,
  });
}

Content-Aware Padding Based on Text Length

Create padding that adapts to localized text:

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

class LocalizedAdaptivePaddingButton extends StatelessWidget {
  final String label;
  final VoidCallback onPressed;
  final bool isLoading;

  const LocalizedAdaptivePaddingButton({
    super.key,
    required this.label,
    required this.onPressed,
    this.isLoading = false,
  });

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

    // Calculate padding based on text length
    final textLength = label.length;
    final basePadding = textLength > 20 ? 12.0 : (textLength > 10 ? 16.0 : 24.0);

    return Semantics(
      button: true,
      enabled: !isLoading,
      label: isLoading ? l10n.buttonLoading : label,
      child: GestureDetector(
        onTap: isLoading ? null : onPressed,
        child: AnimatedPadding(
          duration: const Duration(milliseconds: 200),
          padding: EdgeInsets.symmetric(
            horizontal: isLoading ? basePadding + 8 : basePadding,
            vertical: 8,
          ),
          child: AnimatedContainer(
            duration: const Duration(milliseconds: 200),
            padding: EdgeInsetsDirectional.symmetric(
              horizontal: isLoading ? 20 : 24,
              vertical: 12,
            ),
            decoration: BoxDecoration(
              color: isLoading
                  ? Theme.of(context).colorScheme.surfaceVariant
                  : Theme.of(context).colorScheme.primary,
              borderRadius: BorderRadius.circular(12),
              boxShadow: isLoading
                  ? null
                  : [
                      BoxShadow(
                        color: Theme.of(context)
                            .colorScheme
                            .primary
                            .withOpacity(0.3),
                        blurRadius: 8,
                        offset: const Offset(0, 4),
                      ),
                    ],
            ),
            child: Row(
              mainAxisSize: MainAxisSize.min,
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                if (isLoading) ...[
                  SizedBox(
                    width: 16,
                    height: 16,
                    child: CircularProgressIndicator(
                      strokeWidth: 2,
                      valueColor: AlwaysStoppedAnimation(
                        Theme.of(context).colorScheme.primary,
                      ),
                    ),
                  ),
                  const SizedBox(width: 8),
                ],
                AnimatedDefaultTextStyle(
                  duration: const Duration(milliseconds: 200),
                  style: Theme.of(context).textTheme.labelLarge!.copyWith(
                    color: isLoading
                        ? Theme.of(context).colorScheme.onSurfaceVariant
                        : Theme.of(context).colorScheme.onPrimary,
                    fontWeight: FontWeight.bold,
                  ),
                  child: Text(isLoading ? l10n.loadingText : label),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Collapsible Section with Animated Padding

Create collapsible sections with smooth padding transitions:

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

class LocalizedCollapsibleSection extends StatefulWidget {
  final String title;
  final String content;
  final bool initiallyExpanded;

  const LocalizedCollapsibleSection({
    super.key,
    required this.title,
    required this.content,
    this.initiallyExpanded = false,
  });

  @override
  State<LocalizedCollapsibleSection> createState() =>
      _LocalizedCollapsibleSectionState();
}

class _LocalizedCollapsibleSectionState
    extends State<LocalizedCollapsibleSection>
    with SingleTickerProviderStateMixin {
  late bool _isExpanded;
  late AnimationController _controller;
  late Animation<double> _heightFactor;

  @override
  void initState() {
    super.initState();
    _isExpanded = widget.initiallyExpanded;
    _controller = AnimationController(
      duration: const Duration(milliseconds: 300),
      vsync: this,
    );
    _heightFactor = _controller.drive(CurveTween(curve: Curves.easeInOut));

    if (_isExpanded) {
      _controller.value = 1.0;
    }
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  void _toggle() {
    setState(() {
      _isExpanded = !_isExpanded;
      if (_isExpanded) {
        _controller.forward();
      } else {
        _controller.reverse();
      }
    });
  }

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

    return AnimatedPadding(
      duration: const Duration(milliseconds: 300),
      padding: EdgeInsets.symmetric(
        horizontal: _isExpanded ? 12 : 16,
        vertical: 8,
      ),
      child: AnimatedContainer(
        duration: const Duration(milliseconds: 300),
        decoration: BoxDecoration(
          color: _isExpanded
              ? Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.5)
              : Theme.of(context).colorScheme.surface,
          borderRadius: BorderRadius.circular(_isExpanded ? 16 : 12),
          border: Border.all(
            color: _isExpanded
                ? Theme.of(context).colorScheme.outline.withOpacity(0.3)
                : Theme.of(context).colorScheme.outline.withOpacity(0.1),
          ),
        ),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // Header
            Semantics(
              button: true,
              expanded: _isExpanded,
              label: _isExpanded
                  ? l10n.collapseSectionAccessibility(widget.title)
                  : l10n.expandSectionAccessibility(widget.title),
              child: GestureDetector(
                onTap: _toggle,
                behavior: HitTestBehavior.opaque,
                child: AnimatedPadding(
                  duration: const Duration(milliseconds: 300),
                  padding: EdgeInsetsDirectional.only(
                    start: _isExpanded ? 20 : 16,
                    end: _isExpanded ? 20 : 16,
                    top: _isExpanded ? 16 : 12,
                    bottom: _isExpanded ? 12 : 12,
                  ),
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: [
                      Expanded(
                        child: Text(
                          widget.title,
                          style:
                              Theme.of(context).textTheme.titleMedium?.copyWith(
                            fontWeight: _isExpanded
                                ? FontWeight.bold
                                : FontWeight.normal,
                          ),
                        ),
                      ),
                      AnimatedRotation(
                        turns: _isExpanded ? 0.5 : 0,
                        duration: const Duration(milliseconds: 300),
                        child: Icon(
                          Icons.expand_more,
                          color: Theme.of(context).colorScheme.primary,
                        ),
                      ),
                    ],
                  ),
                ),
              ),
            ),
            // Content
            ClipRect(
              child: AnimatedBuilder(
                animation: _controller,
                builder: (context, child) {
                  return Align(
                    alignment: isRtl
                        ? Alignment.topRight
                        : Alignment.topLeft,
                    heightFactor: _heightFactor.value,
                    child: child,
                  );
                },
                child: AnimatedPadding(
                  duration: const Duration(milliseconds: 300),
                  padding: EdgeInsetsDirectional.only(
                    start: 20,
                    end: 20,
                    bottom: 16,
                  ),
                  child: Text(
                    widget.content,
                    style: Theme.of(context).textTheme.bodyMedium?.copyWith(
                      color: Theme.of(context).colorScheme.onSurfaceVariant,
                    ),
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Complete ARB File for AnimatedPadding

{
  "@@locale": "en",

  "tapToExpand": "Tap to expand",
  "@tapToExpand": {
    "description": "Accessibility hint for expandable element"
  },
  "tapToCollapse": "Tap to collapse",
  "@tapToCollapse": {
    "description": "Accessibility hint for collapsible element"
  },
  "cardTitle": "Feature Overview",
  "cardDescription": "This section contains detailed information about the feature. Additional content is revealed with smooth padding animation.",
  "expandIcon": "Expand",
  "collapseIcon": "Collapse",

  "itemInbox": "Inbox",
  "itemInboxSubtitle": "View your received messages",
  "itemSent": "Sent",
  "itemSentSubtitle": "View messages you've sent",
  "itemDrafts": "Drafts",
  "itemDraftsSubtitle": "Continue editing saved drafts",
  "itemTrash": "Trash",
  "itemTrashSubtitle": "View deleted items",
  "itemSelected": "{item} selected",
  "@itemSelected": {
    "placeholders": {
      "item": {"type": "String"}
    }
  },
  "itemUnselected": "{item}, tap to select",
  "@itemUnselected": {
    "placeholders": {
      "item": {"type": "String"}
    }
  },

  "emailLabel": "Email address",
  "emailHint": "Enter your email",
  "emailRequired": "Email is required",
  "emailInvalid": "Please enter a valid email",
  "passwordLabel": "Password",
  "passwordHint": "Enter your password",
  "passwordRequired": "Password is required",
  "passwordTooShort": "Password must be at least 8 characters",
  "submitButton": "Submit",

  "importanceLow": "Low",
  "importanceMedium": "Medium",
  "importanceHigh": "High",
  "importanceUrgent": "Urgent",
  "notificationAccessibility": "{importance} priority notification: {title}",
  "@notificationAccessibility": {
    "placeholders": {
      "importance": {"type": "String"},
      "title": {"type": "String"}
    }
  },

  "buttonLoading": "Loading, please wait",
  "loadingText": "Loading...",

  "expandSectionAccessibility": "Expand {section} section",
  "@expandSectionAccessibility": {
    "placeholders": {
      "section": {"type": "String"}
    }
  },
  "collapseSectionAccessibility": "Collapse {section} section",
  "@collapseSectionAccessibility": {
    "placeholders": {
      "section": {"type": "String"}
    }
  }
}

Best Practices Summary

  1. Use EdgeInsetsDirectional: Always prefer EdgeInsetsDirectional over EdgeInsets for RTL support
  2. Animate with purpose: Use padding animation to indicate state changes and focus
  3. Consider text expansion: German text can be 30%+ longer than English—ensure padding adapts
  4. Maintain touch targets: Animated padding should never shrink touch targets below 48x48dp
  5. Use appropriate durations: 200-300ms is ideal for padding animations
  6. Combine with other animations: Pair AnimatedPadding with AnimatedContainer for richer effects
  7. Provide accessibility labels: Announce significant padding-related state changes
  8. Test with long text: Verify layouts with languages that have longer text strings
  9. Use semantic spacing: Padding should convey hierarchy and importance
  10. Handle keyboard: Adjust padding when soft keyboard appears

Conclusion

Proper AnimatedPadding localization ensures smooth, responsive spacing that adapts to different languages and text directions. By using directional edge insets, considering text expansion, and maintaining accessibility, you create polished interfaces that feel native to users worldwide. The patterns shown here—expandable cards, selectable lists, animated text fields, and collapsible sections—can be adapted to any Flutter application requiring animated padding transitions.

Remember to test your padding animations with different locales to verify that spacing, touch targets, and accessibility work correctly for all your supported languages.