← Back to Blog

Flutter DefaultTextStyleTransition Localization: Animated Typography for Multilingual Apps

flutterdefaulttextstyletransitionanimationtypographylocalizationaccessibility

Flutter DefaultTextStyleTransition Localization: Animated Typography for Multilingual Apps

DefaultTextStyleTransition provides explicit control over text style animations using an Animation controller. Unlike AnimatedDefaultTextStyle which responds automatically to style changes, DefaultTextStyleTransition gives you precise control over animation timing and curves. This guide covers comprehensive strategies for localizing DefaultTextStyleTransition widgets in Flutter multilingual applications.

Understanding DefaultTextStyleTransition Localization

DefaultTextStyleTransition widgets require localization for:

  • Theme switching: Animated transitions between light/dark modes
  • Reading modes: Comfortable reading, dyslexia-friendly, high contrast
  • Selection states: Highlighting selected text or items
  • Status indicators: Changing text appearance based on status
  • Typography effects: Animated emphasis and text highlights
  • Accessibility modes: Large text and contrast improvements

Basic DefaultTextStyleTransition with Localized Content

Start with a simple text style animation:

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

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

  @override
  State<LocalizedTextStyleTransitionDemo> createState() => _LocalizedTextStyleTransitionDemoState();
}

class _LocalizedTextStyleTransitionDemoState extends State<LocalizedTextStyleTransitionDemo>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<TextStyle> _textStyleAnimation;
  bool _isHighlighted = false;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 400),
    );
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    _textStyleAnimation = TextStyleTween(
      begin: Theme.of(context).textTheme.bodyLarge!.copyWith(
        color: Theme.of(context).colorScheme.onSurface,
      ),
      end: Theme.of(context).textTheme.bodyLarge!.copyWith(
        color: Theme.of(context).colorScheme.primary,
        fontWeight: FontWeight.bold,
        fontSize: 20,
      ),
    ).animate(CurvedAnimation(
      parent: _controller,
      curve: Curves.easeInOut,
    ));
  }

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

  void _toggleHighlight() {
    setState(() {
      _isHighlighted = !_isHighlighted;
      if (_isHighlighted) {
        _controller.forward();
      } else {
        _controller.reverse();
      }
    });
  }

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

    return Scaffold(
      appBar: AppBar(title: Text(l10n.textStyleAnimationTitle)),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              l10n.textStyleDescription,
              style: Theme.of(context).textTheme.bodyMedium,
            ),
            const SizedBox(height: 32),
            GestureDetector(
              onTap: _toggleHighlight,
              child: Semantics(
                label: l10n.animatedTextAccessibility(
                  l10n.sampleText,
                  _isHighlighted ? l10n.highlighted : l10n.normal,
                ),
                child: DefaultTextStyleTransition(
                  style: _textStyleAnimation,
                  child: Text(l10n.sampleText),
                ),
              ),
            ),
            const SizedBox(height: 24),
            Text(
              l10n.tapToToggleHint,
              style: Theme.of(context).textTheme.bodySmall?.copyWith(
                color: Theme.of(context).colorScheme.onSurfaceVariant,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

ARB File Structure for DefaultTextStyleTransition

{
  "textStyleAnimationTitle": "Text Style Animation",
  "@textStyleAnimationTitle": {
    "description": "Title for text style transition demo"
  },
  "textStyleDescription": "Tap the text below to see the animated style change",
  "sampleText": "This text animates when tapped",
  "animatedTextAccessibility": "{text}, style is {state}",
  "@animatedTextAccessibility": {
    "placeholders": {
      "text": {"type": "String"},
      "state": {"type": "String"}
    }
  },
  "highlighted": "highlighted",
  "normal": "normal",
  "tapToToggleHint": "Tap the text to toggle the highlight effect"
}

Reading Mode Selector

Create a reading mode selector with animated text styles:

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

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

  @override
  State<LocalizedReadingModeSelector> createState() => _LocalizedReadingModeSelectorState();
}

class _LocalizedReadingModeSelectorState extends State<LocalizedReadingModeSelector>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  _ReadingMode _currentMode = _ReadingMode.standard;
  _ReadingMode _previousMode = _ReadingMode.standard;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 500),
    );
  }

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

  void _setReadingMode(_ReadingMode mode) {
    if (mode == _currentMode) return;

    setState(() {
      _previousMode = _currentMode;
      _currentMode = mode;
      _controller.forward(from: 0);
    });
  }

  TextStyle _getStyleForMode(_ReadingMode mode, BuildContext context) {
    switch (mode) {
      case _ReadingMode.standard:
        return TextStyle(
          fontSize: 16,
          height: 1.5,
          color: Theme.of(context).colorScheme.onSurface,
          fontFamily: 'Roboto',
        );
      case _ReadingMode.comfortable:
        return TextStyle(
          fontSize: 18,
          height: 1.8,
          color: Theme.of(context).colorScheme.onSurface,
          fontFamily: 'Roboto',
          letterSpacing: 0.3,
        );
      case _ReadingMode.dyslexiaFriendly:
        return TextStyle(
          fontSize: 18,
          height: 2.0,
          color: Theme.of(context).colorScheme.onSurface,
          fontFamily: 'OpenDyslexic',
          letterSpacing: 0.5,
          wordSpacing: 2.0,
        );
      case _ReadingMode.highContrast:
        return TextStyle(
          fontSize: 18,
          height: 1.6,
          color: Colors.black,
          fontWeight: FontWeight.w500,
          fontFamily: 'Roboto',
        );
    }
  }

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

    final textStyleAnimation = TextStyleTween(
      begin: _getStyleForMode(_previousMode, context),
      end: _getStyleForMode(_currentMode, context),
    ).animate(CurvedAnimation(
      parent: _controller,
      curve: Curves.easeInOut,
    ));

    return Scaffold(
      appBar: AppBar(title: Text(l10n.readingModeTitle)),
      body: Column(
        children: [
          // Reading mode selector
          Container(
            padding: const EdgeInsets.all(16),
            decoration: BoxDecoration(
              color: Theme.of(context).colorScheme.surfaceContainerHighest,
            ),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  l10n.selectReadingMode,
                  style: Theme.of(context).textTheme.titleSmall,
                ),
                const SizedBox(height: 12),
                SingleChildScrollView(
                  scrollDirection: Axis.horizontal,
                  child: Row(
                    children: [
                      _ReadingModeChip(
                        label: l10n.standardMode,
                        icon: Icons.text_fields,
                        isSelected: _currentMode == _ReadingMode.standard,
                        onTap: () => _setReadingMode(_ReadingMode.standard),
                        l10n: l10n,
                      ),
                      const SizedBox(width: 8),
                      _ReadingModeChip(
                        label: l10n.comfortableMode,
                        icon: Icons.format_size,
                        isSelected: _currentMode == _ReadingMode.comfortable,
                        onTap: () => _setReadingMode(_ReadingMode.comfortable),
                        l10n: l10n,
                      ),
                      const SizedBox(width: 8),
                      _ReadingModeChip(
                        label: l10n.dyslexiaFriendlyMode,
                        icon: Icons.accessibility,
                        isSelected: _currentMode == _ReadingMode.dyslexiaFriendly,
                        onTap: () => _setReadingMode(_ReadingMode.dyslexiaFriendly),
                        l10n: l10n,
                      ),
                      const SizedBox(width: 8),
                      _ReadingModeChip(
                        label: l10n.highContrastMode,
                        icon: Icons.contrast,
                        isSelected: _currentMode == _ReadingMode.highContrast,
                        onTap: () => _setReadingMode(_ReadingMode.highContrast),
                        l10n: l10n,
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ),
          // Article content with animated text style
          Expanded(
            child: Container(
              color: _currentMode == _ReadingMode.highContrast
                  ? Colors.white
                  : Theme.of(context).colorScheme.surface,
              child: SingleChildScrollView(
                padding: const EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      l10n.articleTitle,
                      style: Theme.of(context).textTheme.headlineMedium?.copyWith(
                        color: _currentMode == _ReadingMode.highContrast
                            ? Colors.black
                            : null,
                      ),
                    ),
                    const SizedBox(height: 8),
                    Text(
                      l10n.articleMeta,
                      style: Theme.of(context).textTheme.bodySmall?.copyWith(
                        color: _currentMode == _ReadingMode.highContrast
                            ? Colors.black87
                            : Theme.of(context).colorScheme.onSurfaceVariant,
                      ),
                    ),
                    const SizedBox(height: 24),
                    DefaultTextStyleTransition(
                      style: textStyleAnimation,
                      child: Text(l10n.articleParagraph1),
                    ),
                    const SizedBox(height: 16),
                    DefaultTextStyleTransition(
                      style: textStyleAnimation,
                      child: Text(l10n.articleParagraph2),
                    ),
                    const SizedBox(height: 16),
                    DefaultTextStyleTransition(
                      style: textStyleAnimation,
                      child: Text(l10n.articleParagraph3),
                    ),
                  ],
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

enum _ReadingMode {
  standard,
  comfortable,
  dyslexiaFriendly,
  highContrast,
}

class _ReadingModeChip extends StatelessWidget {
  final String label;
  final IconData icon;
  final bool isSelected;
  final VoidCallback onTap;
  final AppLocalizations l10n;

  const _ReadingModeChip({
    required this.label,
    required this.icon,
    required this.isSelected,
    required this.onTap,
    required this.l10n,
  });

  @override
  Widget build(BuildContext context) {
    return Semantics(
      button: true,
      selected: isSelected,
      label: l10n.readingModeAccessibility(label, isSelected ? l10n.selected : l10n.notSelected),
      child: InkWell(
        onTap: onTap,
        borderRadius: BorderRadius.circular(20),
        child: AnimatedContainer(
          duration: const Duration(milliseconds: 200),
          padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
          decoration: BoxDecoration(
            color: isSelected
                ? Theme.of(context).colorScheme.primary
                : Theme.of(context).colorScheme.surface,
            borderRadius: BorderRadius.circular(20),
            border: Border.all(
              color: isSelected
                  ? Theme.of(context).colorScheme.primary
                  : Theme.of(context).colorScheme.outline,
            ),
          ),
          child: Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              Icon(
                icon,
                size: 18,
                color: isSelected
                    ? Theme.of(context).colorScheme.onPrimary
                    : Theme.of(context).colorScheme.onSurface,
              ),
              const SizedBox(width: 8),
              Text(
                label,
                style: TextStyle(
                  color: isSelected
                      ? Theme.of(context).colorScheme.onPrimary
                      : Theme.of(context).colorScheme.onSurface,
                  fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Animated Status Text

Create status text with animated style changes:

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

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

  @override
  State<LocalizedAnimatedStatusText> createState() => _LocalizedAnimatedStatusTextState();
}

class _LocalizedAnimatedStatusTextState extends State<LocalizedAnimatedStatusText>
    with TickerProviderStateMixin {
  late List<AnimationController> _controllers;
  final List<_OrderItem> _orders = [];

  @override
  void initState() {
    super.initState();
    _controllers = [];
  }

  @override
  void dispose() {
    for (final controller in _controllers) {
      controller.dispose();
    }
    super.dispose();
  }

  void _addOrder(AppLocalizations l10n) {
    final controller = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 400),
    );
    _controllers.add(controller);

    setState(() {
      _orders.add(_OrderItem(
        id: 'ORD-${DateTime.now().millisecondsSinceEpoch}',
        name: l10n.sampleOrderName,
        status: _OrderStatus.pending,
        controller: controller,
      ));
    });

    // Simulate status changes
    _simulateStatusChanges(_orders.length - 1, l10n);
  }

  void _simulateStatusChanges(int index, AppLocalizations l10n) async {
    await Future.delayed(const Duration(seconds: 2));
    if (!mounted || index >= _orders.length) return;

    setState(() {
      _orders[index] = _orders[index].copyWith(status: _OrderStatus.processing);
    });
    _orders[index].controller.forward(from: 0);

    await Future.delayed(const Duration(seconds: 2));
    if (!mounted || index >= _orders.length) return;

    setState(() {
      _orders[index] = _orders[index].copyWith(status: _OrderStatus.shipped);
    });
    _orders[index].controller.forward(from: 0);

    await Future.delayed(const Duration(seconds: 2));
    if (!mounted || index >= _orders.length) return;

    setState(() {
      _orders[index] = _orders[index].copyWith(status: _OrderStatus.delivered);
    });
    _orders[index].controller.forward(from: 0);
  }

  TextStyle _getStatusStyle(_OrderStatus status, BuildContext context) {
    switch (status) {
      case _OrderStatus.pending:
        return TextStyle(
          color: Theme.of(context).colorScheme.onSurfaceVariant,
          fontSize: 14,
          fontWeight: FontWeight.normal,
        );
      case _OrderStatus.processing:
        return TextStyle(
          color: Colors.orange.shade700,
          fontSize: 14,
          fontWeight: FontWeight.w500,
        );
      case _OrderStatus.shipped:
        return TextStyle(
          color: Colors.blue.shade700,
          fontSize: 14,
          fontWeight: FontWeight.w600,
        );
      case _OrderStatus.delivered:
        return TextStyle(
          color: Colors.green.shade700,
          fontSize: 14,
          fontWeight: FontWeight.bold,
        );
    }
  }

  String _getStatusText(_OrderStatus status, AppLocalizations l10n) {
    switch (status) {
      case _OrderStatus.pending:
        return l10n.statusPending;
      case _OrderStatus.processing:
        return l10n.statusProcessing;
      case _OrderStatus.shipped:
        return l10n.statusShipped;
      case _OrderStatus.delivered:
        return l10n.statusDelivered;
    }
  }

  IconData _getStatusIcon(_OrderStatus status) {
    switch (status) {
      case _OrderStatus.pending:
        return Icons.schedule;
      case _OrderStatus.processing:
        return Icons.sync;
      case _OrderStatus.shipped:
        return Icons.local_shipping;
      case _OrderStatus.delivered:
        return Icons.check_circle;
    }
  }

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

    return Scaffold(
      appBar: AppBar(title: Text(l10n.orderStatusTitle)),
      floatingActionButton: FloatingActionButton.extended(
        onPressed: () => _addOrder(l10n),
        icon: const Icon(Icons.add),
        label: Text(l10n.addOrder),
      ),
      body: _orders.isEmpty
          ? Center(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  Icon(
                    Icons.shopping_bag_outlined,
                    size: 64,
                    color: Theme.of(context).colorScheme.onSurfaceVariant,
                  ),
                  const SizedBox(height: 16),
                  Text(
                    l10n.noOrdersMessage,
                    style: Theme.of(context).textTheme.bodyLarge?.copyWith(
                      color: Theme.of(context).colorScheme.onSurfaceVariant,
                    ),
                  ),
                ],
              ),
            )
          : ListView.builder(
              padding: const EdgeInsets.all(16),
              itemCount: _orders.length,
              itemBuilder: (context, index) {
                final order = _orders[index];
                final statusStyle = _getStatusStyle(order.status, context);

                final textStyleAnimation = TextStyleTween(
                  begin: statusStyle,
                  end: statusStyle,
                ).animate(order.controller);

                return Card(
                  margin: const EdgeInsets.only(bottom: 12),
                  child: Padding(
                    padding: const EdgeInsets.all(16),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Row(
                          children: [
                            Expanded(
                              child: Column(
                                crossAxisAlignment: CrossAxisAlignment.start,
                                children: [
                                  Text(
                                    order.name,
                                    style: Theme.of(context).textTheme.titleMedium,
                                  ),
                                  Text(
                                    order.id,
                                    style: Theme.of(context).textTheme.bodySmall?.copyWith(
                                      color: Theme.of(context).colorScheme.onSurfaceVariant,
                                    ),
                                  ),
                                ],
                              ),
                            ),
                            AnimatedContainer(
                              duration: const Duration(milliseconds: 300),
                              padding: const EdgeInsets.symmetric(
                                horizontal: 12,
                                vertical: 6,
                              ),
                              decoration: BoxDecoration(
                                color: _getStatusColor(order.status).withOpacity(0.1),
                                borderRadius: BorderRadius.circular(16),
                              ),
                              child: Row(
                                mainAxisSize: MainAxisSize.min,
                                children: [
                                  Icon(
                                    _getStatusIcon(order.status),
                                    size: 16,
                                    color: _getStatusColor(order.status),
                                  ),
                                  const SizedBox(width: 6),
                                  Semantics(
                                    label: l10n.orderStatusAccessibility(
                                      order.id,
                                      _getStatusText(order.status, l10n),
                                    ),
                                    child: DefaultTextStyleTransition(
                                      style: textStyleAnimation,
                                      child: Text(_getStatusText(order.status, l10n)),
                                    ),
                                  ),
                                ],
                              ),
                            ),
                          ],
                        ),
                        const SizedBox(height: 16),
                        // Progress indicator
                        _OrderProgressIndicator(
                          status: order.status,
                          l10n: l10n,
                        ),
                      ],
                    ),
                  ),
                );
              },
            ),
    );
  }

  Color _getStatusColor(_OrderStatus status) {
    switch (status) {
      case _OrderStatus.pending:
        return Colors.grey;
      case _OrderStatus.processing:
        return Colors.orange;
      case _OrderStatus.shipped:
        return Colors.blue;
      case _OrderStatus.delivered:
        return Colors.green;
    }
  }
}

enum _OrderStatus {
  pending,
  processing,
  shipped,
  delivered,
}

class _OrderItem {
  final String id;
  final String name;
  final _OrderStatus status;
  final AnimationController controller;

  _OrderItem({
    required this.id,
    required this.name,
    required this.status,
    required this.controller,
  });

  _OrderItem copyWith({_OrderStatus? status}) {
    return _OrderItem(
      id: id,
      name: name,
      status: status ?? this.status,
      controller: controller,
    );
  }
}

class _OrderProgressIndicator extends StatelessWidget {
  final _OrderStatus status;
  final AppLocalizations l10n;

  const _OrderProgressIndicator({
    required this.status,
    required this.l10n,
  });

  @override
  Widget build(BuildContext context) {
    final steps = [
      _OrderStatus.pending,
      _OrderStatus.processing,
      _OrderStatus.shipped,
      _OrderStatus.delivered,
    ];

    final currentIndex = steps.indexOf(status);

    return Row(
      children: steps.asMap().entries.map((entry) {
        final index = entry.key;
        final step = entry.value;
        final isCompleted = index <= currentIndex;
        final isLast = index == steps.length - 1;

        return Expanded(
          child: Row(
            children: [
              AnimatedContainer(
                duration: const Duration(milliseconds: 300),
                width: 24,
                height: 24,
                decoration: BoxDecoration(
                  color: isCompleted
                      ? _getStepColor(step)
                      : Theme.of(context).colorScheme.surfaceContainerHighest,
                  shape: BoxShape.circle,
                ),
                child: Center(
                  child: isCompleted
                      ? Icon(
                          Icons.check,
                          size: 14,
                          color: Colors.white,
                        )
                      : Text(
                          '${index + 1}',
                          style: TextStyle(
                            fontSize: 12,
                            color: Theme.of(context).colorScheme.onSurfaceVariant,
                          ),
                        ),
                ),
              ),
              if (!isLast)
                Expanded(
                  child: AnimatedContainer(
                    duration: const Duration(milliseconds: 300),
                    height: 2,
                    color: index < currentIndex
                        ? _getStepColor(steps[index + 1])
                        : Theme.of(context).colorScheme.surfaceContainerHighest,
                  ),
                ),
            ],
          ),
        );
      }).toList(),
    );
  }

  Color _getStepColor(_OrderStatus status) {
    switch (status) {
      case _OrderStatus.pending:
        return Colors.grey;
      case _OrderStatus.processing:
        return Colors.orange;
      case _OrderStatus.shipped:
        return Colors.blue;
      case _OrderStatus.delivered:
        return Colors.green;
    }
  }
}

Animated Quote Highlight

Create an animated quote with emphasis:

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

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

  @override
  State<LocalizedAnimatedQuote> createState() => _LocalizedAnimatedQuoteState();
}

class _LocalizedAnimatedQuoteState extends State<LocalizedAnimatedQuote>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  int _currentQuoteIndex = 0;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 600),
    );
    _controller.forward();
  }

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

  void _nextQuote(int totalQuotes) {
    _controller.reverse().then((_) {
      setState(() {
        _currentQuoteIndex = (_currentQuoteIndex + 1) % totalQuotes;
      });
      _controller.forward();
    });
  }

  void _previousQuote(int totalQuotes) {
    _controller.reverse().then((_) {
      setState(() {
        _currentQuoteIndex = (_currentQuoteIndex - 1 + totalQuotes) % totalQuotes;
      });
      _controller.forward();
    });
  }

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

    final quotes = [
      _Quote(
        text: l10n.quote1Text,
        author: l10n.quote1Author,
        emphasis: l10n.quote1Emphasis,
      ),
      _Quote(
        text: l10n.quote2Text,
        author: l10n.quote2Author,
        emphasis: l10n.quote2Emphasis,
      ),
      _Quote(
        text: l10n.quote3Text,
        author: l10n.quote3Author,
        emphasis: l10n.quote3Emphasis,
      ),
    ];

    final currentQuote = quotes[_currentQuoteIndex];

    final quoteStyleAnimation = TextStyleTween(
      begin: TextStyle(
        fontSize: 20,
        fontStyle: FontStyle.italic,
        color: Theme.of(context).colorScheme.onSurface.withOpacity(0),
        height: 1.6,
      ),
      end: TextStyle(
        fontSize: 24,
        fontStyle: FontStyle.italic,
        color: Theme.of(context).colorScheme.onSurface,
        height: 1.6,
      ),
    ).animate(CurvedAnimation(
      parent: _controller,
      curve: Curves.easeOutCubic,
    ));

    final authorStyleAnimation = TextStyleTween(
      begin: TextStyle(
        fontSize: 14,
        fontWeight: FontWeight.w500,
        color: Theme.of(context).colorScheme.primary.withOpacity(0),
      ),
      end: TextStyle(
        fontSize: 16,
        fontWeight: FontWeight.w600,
        color: Theme.of(context).colorScheme.primary,
      ),
    ).animate(CurvedAnimation(
      parent: _controller,
      curve: const Interval(0.3, 1.0, curve: Curves.easeOut),
    ));

    return Scaffold(
      appBar: AppBar(title: Text(l10n.dailyQuotesTitle)),
      body: Padding(
        padding: const EdgeInsets.all(24),
        child: Column(
          children: [
            Expanded(
              child: Center(
                child: Semantics(
                  label: l10n.quoteAccessibility(
                    currentQuote.text,
                    currentQuote.author,
                  ),
                  child: Card(
                    elevation: 4,
                    child: Padding(
                      padding: const EdgeInsets.all(32),
                      child: Column(
                        mainAxisSize: MainAxisSize.min,
                        children: [
                          Icon(
                            Icons.format_quote,
                            size: 48,
                            color: Theme.of(context).colorScheme.primary.withOpacity(0.3),
                          ),
                          const SizedBox(height: 24),
                          DefaultTextStyleTransition(
                            style: quoteStyleAnimation,
                            child: Text(
                              currentQuote.text,
                              textAlign: TextAlign.center,
                            ),
                          ),
                          const SizedBox(height: 24),
                          DefaultTextStyleTransition(
                            style: authorStyleAnimation,
                            child: Text('— ${currentQuote.author}'),
                          ),
                          if (currentQuote.emphasis.isNotEmpty) ...[
                            const SizedBox(height: 16),
                            Container(
                              padding: const EdgeInsets.symmetric(
                                horizontal: 12,
                                vertical: 6,
                              ),
                              decoration: BoxDecoration(
                                color: Theme.of(context).colorScheme.primaryContainer,
                                borderRadius: BorderRadius.circular(16),
                              ),
                              child: Text(
                                currentQuote.emphasis,
                                style: TextStyle(
                                  fontSize: 12,
                                  color: Theme.of(context).colorScheme.onPrimaryContainer,
                                ),
                              ),
                            ),
                          ],
                        ],
                      ),
                    ),
                  ),
                ),
              ),
            ),
            const SizedBox(height: 24),
            // Navigation
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                IconButton.outlined(
                  onPressed: () => _previousQuote(quotes.length),
                  icon: const Icon(Icons.arrow_back),
                  tooltip: l10n.previousQuote,
                ),
                const SizedBox(width: 16),
                // Dots indicator
                Row(
                  children: quotes.asMap().entries.map((entry) {
                    final index = entry.key;
                    final isActive = index == _currentQuoteIndex;
                    return Container(
                      margin: const EdgeInsets.symmetric(horizontal: 4),
                      width: isActive ? 24 : 8,
                      height: 8,
                      decoration: BoxDecoration(
                        color: isActive
                            ? Theme.of(context).colorScheme.primary
                            : Theme.of(context).colorScheme.surfaceContainerHighest,
                        borderRadius: BorderRadius.circular(4),
                      ),
                    );
                  }).toList(),
                ),
                const SizedBox(width: 16),
                IconButton.outlined(
                  onPressed: () => _nextQuote(quotes.length),
                  icon: const Icon(Icons.arrow_forward),
                  tooltip: l10n.nextQuote,
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

class _Quote {
  final String text;
  final String author;
  final String emphasis;

  _Quote({
    required this.text,
    required this.author,
    this.emphasis = '',
  });
}

Animated Language Selector

Create a language selector with animated text:

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

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

  @override
  State<LocalizedLanguageSelector> createState() => _LocalizedLanguageSelectorState();
}

class _LocalizedLanguageSelectorState extends State<LocalizedLanguageSelector>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  String _selectedLanguage = 'en';

  final List<_Language> _languages = [
    _Language('en', 'English', 'Welcome to our app'),
    _Language('es', 'Español', 'Bienvenido a nuestra app'),
    _Language('fr', 'Français', 'Bienvenue dans notre app'),
    _Language('de', 'Deutsch', 'Willkommen in unserer App'),
    _Language('ar', 'العربية', 'مرحباً بك في تطبيقنا'),
    _Language('ja', '日本語', 'アプリへようこそ'),
  ];

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 400),
    );
  }

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

  void _selectLanguage(String code) {
    if (code == _selectedLanguage) return;

    _controller.forward(from: 0).then((_) {
      setState(() {
        _selectedLanguage = code;
      });
    });
  }

  _Language get _currentLanguage =>
      _languages.firstWhere((l) => l.code == _selectedLanguage);

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

    final welcomeStyleAnimation = TextStyleTween(
      begin: Theme.of(context).textTheme.headlineMedium!.copyWith(
        color: Theme.of(context).colorScheme.onSurface,
      ),
      end: Theme.of(context).textTheme.headlineMedium!.copyWith(
        color: Theme.of(context).colorScheme.primary,
        fontWeight: FontWeight.bold,
      ),
    ).animate(CurvedAnimation(
      parent: _controller,
      curve: Curves.easeInOut,
    ));

    return Scaffold(
      appBar: AppBar(title: Text(l10n.languageSelectorTitle)),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              l10n.selectLanguageLabel,
              style: Theme.of(context).textTheme.titleMedium,
            ),
            const SizedBox(height: 16),
            Wrap(
              spacing: 8,
              runSpacing: 8,
              children: _languages.map((language) {
                final isSelected = language.code == _selectedLanguage;
                return Semantics(
                  button: true,
                  selected: isSelected,
                  label: l10n.languageOptionAccessibility(
                    language.name,
                    isSelected ? l10n.selected : l10n.notSelected,
                  ),
                  child: InkWell(
                    onTap: () => _selectLanguage(language.code),
                    borderRadius: BorderRadius.circular(8),
                    child: AnimatedContainer(
                      duration: const Duration(milliseconds: 200),
                      padding: const EdgeInsets.symmetric(
                        horizontal: 16,
                        vertical: 12,
                      ),
                      decoration: BoxDecoration(
                        color: isSelected
                            ? Theme.of(context).colorScheme.primaryContainer
                            : Theme.of(context).colorScheme.surface,
                        border: Border.all(
                          color: isSelected
                              ? Theme.of(context).colorScheme.primary
                              : Theme.of(context).colorScheme.outline,
                          width: isSelected ? 2 : 1,
                        ),
                        borderRadius: BorderRadius.circular(8),
                      ),
                      child: Text(
                        language.name,
                        style: TextStyle(
                          color: isSelected
                              ? Theme.of(context).colorScheme.onPrimaryContainer
                              : Theme.of(context).colorScheme.onSurface,
                          fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
                        ),
                      ),
                    ),
                  ),
                );
              }).toList(),
            ),
            const SizedBox(height: 48),
            // Preview section
            Card(
              child: Padding(
                padding: const EdgeInsets.all(24),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Row(
                      children: [
                        Icon(
                          Icons.preview,
                          color: Theme.of(context).colorScheme.primary,
                        ),
                        const SizedBox(width: 8),
                        Text(
                          l10n.previewLabel,
                          style: Theme.of(context).textTheme.titleSmall,
                        ),
                      ],
                    ),
                    const SizedBox(height: 16),
                    DefaultTextStyleTransition(
                      style: welcomeStyleAnimation,
                      textAlign: _currentLanguage.code == 'ar'
                          ? TextAlign.right
                          : TextAlign.left,
                      child: SizedBox(
                        width: double.infinity,
                        child: Text(
                          _currentLanguage.welcomeText,
                          textDirection: _currentLanguage.code == 'ar'
                              ? TextDirection.rtl
                              : TextDirection.ltr,
                        ),
                      ),
                    ),
                    const SizedBox(height: 8),
                    Text(
                      l10n.languageCode(_currentLanguage.code),
                      style: Theme.of(context).textTheme.bodySmall?.copyWith(
                        color: Theme.of(context).colorScheme.onSurfaceVariant,
                      ),
                    ),
                  ],
                ),
              ),
            ),
            const Spacer(),
            SizedBox(
              width: double.infinity,
              child: ElevatedButton(
                onPressed: () {
                  ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(
                      content: Text(l10n.languageAppliedMessage(_currentLanguage.name)),
                    ),
                  );
                },
                child: Text(l10n.applyLanguageButton),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class _Language {
  final String code;
  final String name;
  final String welcomeText;

  _Language(this.code, this.name, this.welcomeText);
}

Complete ARB File for DefaultTextStyleTransition

{
  "@@locale": "en",

  "textStyleAnimationTitle": "Text Style Animation",
  "textStyleDescription": "Tap the text below to see the animated style change",
  "sampleText": "This text animates when tapped",
  "animatedTextAccessibility": "{text}, style is {state}",
  "@animatedTextAccessibility": {
    "placeholders": {
      "text": {"type": "String"},
      "state": {"type": "String"}
    }
  },
  "highlighted": "highlighted",
  "normal": "normal",
  "tapToToggleHint": "Tap the text to toggle the highlight effect",

  "readingModeTitle": "Reading Mode",
  "selectReadingMode": "Select Reading Mode",
  "standardMode": "Standard",
  "comfortableMode": "Comfortable",
  "dyslexiaFriendlyMode": "Dyslexia",
  "highContrastMode": "High Contrast",
  "readingModeAccessibility": "{mode}, {state}",
  "@readingModeAccessibility": {
    "placeholders": {
      "mode": {"type": "String"},
      "state": {"type": "String"}
    }
  },
  "selected": "selected",
  "notSelected": "not selected",
  "articleTitle": "The Art of Multilingual Design",
  "articleMeta": "Published on January 23, 2026 • 5 min read",
  "articleParagraph1": "Creating applications that work seamlessly across multiple languages requires careful consideration of typography, layout, and user experience. Each language brings its own unique characteristics that must be respected in the design process.",
  "articleParagraph2": "When designing for international audiences, it's essential to consider text expansion, right-to-left layouts, and cultural nuances. A word that takes up little space in English might require significantly more space in German, while Arabic and Hebrew require complete layout mirroring.",
  "articleParagraph3": "Accessibility is another crucial factor. Different reading modes help users with various needs, from those who prefer larger text to those with dyslexia who benefit from specialized fonts and increased spacing. By implementing animated transitions between these modes, we create a smoother, more pleasant user experience.",

  "orderStatusTitle": "Order Tracking",
  "addOrder": "Add Order",
  "noOrdersMessage": "No orders yet. Add one to see status animations.",
  "sampleOrderName": "Premium Wireless Headphones",
  "statusPending": "Pending",
  "statusProcessing": "Processing",
  "statusShipped": "Shipped",
  "statusDelivered": "Delivered",
  "orderStatusAccessibility": "Order {id} status: {status}",
  "@orderStatusAccessibility": {
    "placeholders": {
      "id": {"type": "String"},
      "status": {"type": "String"}
    }
  },

  "dailyQuotesTitle": "Daily Inspiration",
  "previousQuote": "Previous quote",
  "nextQuote": "Next quote",
  "quoteAccessibility": "Quote: {text}, by {author}",
  "@quoteAccessibility": {
    "placeholders": {
      "text": {"type": "String"},
      "author": {"type": "String"}
    }
  },
  "quote1Text": "The only way to do great work is to love what you do.",
  "quote1Author": "Steve Jobs",
  "quote1Emphasis": "Passion",
  "quote2Text": "Innovation distinguishes between a leader and a follower.",
  "quote2Author": "Steve Jobs",
  "quote2Emphasis": "Leadership",
  "quote3Text": "Stay hungry, stay foolish.",
  "quote3Author": "Steve Jobs",
  "quote3Emphasis": "Curiosity",

  "languageSelectorTitle": "Language Settings",
  "selectLanguageLabel": "Choose your preferred language",
  "languageOptionAccessibility": "{language}, {state}",
  "@languageOptionAccessibility": {
    "placeholders": {
      "language": {"type": "String"},
      "state": {"type": "String"}
    }
  },
  "previewLabel": "Preview",
  "languageCode": "Language code: {code}",
  "@languageCode": {
    "placeholders": {
      "code": {"type": "String"}
    }
  },
  "applyLanguageButton": "Apply Language",
  "languageAppliedMessage": "{language} has been applied",
  "@languageAppliedMessage": {
    "placeholders": {
      "language": {"type": "String"}
    }
  }
}

Best Practices Summary

  1. Use TextStyleTween: Provides smooth interpolation between text styles
  2. Handle font loading: Ensure custom fonts are loaded before animating
  3. Consider text direction: RTL languages may need different alignment
  4. Combine with opacity: Fade effects enhance text style transitions
  5. Dispose controllers: Always dispose AnimationControllers
  6. Add accessibility labels: Use Semantics with state information
  7. Test with different lengths: Ensure animations work with varying text
  8. Use appropriate durations: 300-500ms works well for text transitions
  9. Consider readability: Don't animate too frequently during reading
  10. Cache text styles: Create styles once when possible for performance

Conclusion

DefaultTextStyleTransition provides precise control over typography animations using explicit AnimationControllers. By combining smooth text style transitions with proper localization support, you can create reading modes, status indicators, quote displays, and language selectors that enhance the user experience across all languages. The key is using TextStyleTween for smooth interpolation and ensuring accessibility is maintained throughout the animation.

Remember to always dispose of your AnimationControllers and consider the readability impact when animating text that users need to read continuously.