skills/flutter-ui-ux/references/theme-templates.md

19 KiB

Flutter Theme Templates

Material Design 3 Themes

Modern Material You implementation.

1. Dynamic Color Theme

Adaptive colors based on system preferences.

class AppTheme {
  static ThemeData lightTheme(ColorScheme? dynamicColorScheme) {
    final colorScheme = dynamicColorScheme ??
      ColorScheme.fromSeed(
        seedColor: Colors.blue,
        brightness: Brightness.light,
      );

    return ThemeData(
      useMaterial3: true,
      colorScheme: colorScheme,
      brightness: Brightness.light,

      // Typography
      textTheme: _buildTextTheme(colorScheme),

      // Component themes
      elevatedButtonTheme: _elevatedButtonTheme(colorScheme),
      cardTheme: _cardTheme(colorScheme),
      appBarTheme: _appBarTheme(colorScheme),
      bottomNavigationBarTheme: _bottomNavTheme(colorScheme),
    );
  }

  static ThemeData darkTheme(ColorScheme? dynamicColorScheme) {
    final colorScheme = dynamicColorScheme ??
      ColorScheme.fromSeed(
        seedColor: Colors.blue,
        brightness: Brightness.dark,
      );

    return ThemeData(
      useMaterial3: true,
      colorScheme: colorScheme,
      brightness: Brightness.dark,

      textTheme: _buildTextTheme(colorScheme),
      elevatedButtonTheme: _elevatedButtonTheme(colorScheme),
      cardTheme: _cardTheme(colorScheme),
      appBarTheme: _appBarTheme(colorScheme),
      bottomNavigationBarTheme: _bottomNavTheme(colorScheme),
    );
  }

  static TextTheme _buildTextTheme(ColorScheme colorScheme) {
    return TextTheme(
      displayLarge: TextStyle(
        fontSize: 57,
        fontWeight: FontWeight.w400,
        color: colorScheme.onSurface,
      ),
      displayMedium: TextStyle(
        fontSize: 45,
        fontWeight: FontWeight.w400,
        color: colorScheme.onSurface,
      ),
      headlineLarge: TextStyle(
        fontSize: 32,
        fontWeight: FontWeight.w400,
        color: colorScheme.onSurface,
      ),
      headlineMedium: TextStyle(
        fontSize: 28,
        fontWeight: FontWeight.w400,
        color: colorScheme.onSurface,
      ),
      titleLarge: TextStyle(
        fontSize: 22,
        fontWeight: FontWeight.w500,
        color: colorScheme.onSurface,
      ),
      titleMedium: TextStyle(
        fontSize: 16,
        fontWeight: FontWeight.w500,
        color: colorScheme.onSurface,
      ),
      bodyLarge: TextStyle(
        fontSize: 16,
        fontWeight: FontWeight.w400,
        color: colorScheme.onSurface,
      ),
      bodyMedium: TextStyle(
        fontSize: 14,
        fontWeight: FontWeight.w400,
        color: colorScheme.onSurface,
      ),
      labelLarge: TextStyle(
        fontSize: 14,
        fontWeight: FontWeight.w500,
        color: colorScheme.onPrimary,
      ),
    );
  }

  static ElevatedButtonThemeData _elevatedButtonTheme(ColorScheme colorScheme) {
    return ElevatedButtonThemeData(
      style: ElevatedButton.styleFrom(
        backgroundColor: colorScheme.primary,
        foregroundColor: colorScheme.onPrimary,
        elevation: 2,
        padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(20),
        ),
      ),
    );
  }

  static CardTheme _cardTheme(ColorScheme colorScheme) {
    return CardTheme(
      elevation: 2,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(16),
      ),
      color: colorScheme.surface,
    );
  }

  static AppBarTheme _appBarTheme(ColorScheme colorScheme) {
    return AppBarTheme(
      backgroundColor: colorScheme.surface,
      foregroundColor: colorScheme.onSurface,
      elevation: 0,
      centerTitle: true,
      titleTextStyle: TextStyle(
        fontSize: 22,
        fontWeight: FontWeight.w500,
        color: colorScheme.onSurface,
      ),
    );
  }

  static BottomNavigationBarThemeData _bottomNavTheme(ColorScheme colorScheme) {
    return BottomNavigationBarThemeData(
      backgroundColor: colorScheme.surface,
      selectedItemColor: colorScheme.primary,
      unselectedItemColor: colorScheme.onSurfaceVariant,
      type: BottomNavigationBarType.fixed,
      elevation: 8,
      landscapeLayout: BottomNavigationBarLandscapeLayout.centered,
    );
  }
}

2. Brand Theme

Custom brand colors and styling.

class BrandTheme {
  // Brand colors
  static const Color primaryBrand = Color(0xFF2E7D32); // Green
  static const Color secondaryBrand = Color(0xFF1976D2); // Blue
  static const Color accentBrand = Color(0xFFFF6F00); // Orange

  static const Color lightBackground = Color(0xFFF5F5F5);
  static const Color darkBackground = Color(0xFF121212);

  static ThemeData lightBrandTheme() {
    final colorScheme = ColorScheme.fromSeed(
      seedColor: primaryBrand,
      brightness: Brightness.light,
      primary: primaryBrand,
      secondary: secondaryBrand,
      tertiary: accentBrand,
    );

    return ThemeData(
      useMaterial3: true,
      colorScheme: colorScheme,

      // Custom extensions
      extensions: [
        _BrandColors(
          primary: primaryBrand,
          secondary: secondaryBrand,
          accent: accentBrand,
          background: lightBackground,
        ),
      ],

      // Component themes
      elevatedButtonTheme: _brandElevatedButton(colorScheme),
      outlinedButtonTheme: _brandOutlinedButton(colorScheme),
      textButtonTheme: _brandTextButton(colorScheme),
      inputDecorationTheme: _brandInputDecoration(colorScheme),
    );
  }

  static ThemeData darkBrandTheme() {
    final colorScheme = ColorScheme.fromSeed(
      seedColor: primaryBrand,
      brightness: Brightness.dark,
      primary: primaryBrand,
      secondary: secondaryBrand,
      tertiary: accentBrand,
    );

    return ThemeData(
      useMaterial3: true,
      colorScheme: colorScheme,

      extensions: [
        _BrandColors(
          primary: primaryBrand,
          secondary: secondaryBrand,
          accent: accentBrand,
          background: darkBackground,
        ),
      ],

      elevatedButtonTheme: _brandElevatedButton(colorScheme),
      outlinedButtonTheme: _brandOutlinedButton(colorScheme),
      textButtonTheme: _brandTextButton(colorScheme),
      inputDecorationTheme: _brandInputDecoration(colorScheme),
    );
  }

  static ElevatedButtonThemeData _brandElevatedButton(ColorScheme colorScheme) {
    return ElevatedButtonThemeData(
      style: ElevatedButton.styleFrom(
        backgroundColor: primaryBrand,
        foregroundColor: Colors.white,
        elevation: 4,
        shadowColor: primaryBrand.withOpacity(0.3),
        padding: EdgeInsets.symmetric(horizontal: 32, vertical: 16),
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(12),
        ),
      ),
    );
  }

  static OutlinedButtonThemeData _brandOutlinedButton(ColorScheme colorScheme) {
    return OutlinedButtonThemeData(
      style: OutlinedButton.styleFrom(
        foregroundColor: primaryBrand,
        side: BorderSide(color: primaryBrand, width: 2),
        padding: EdgeInsets.symmetric(horizontal: 32, vertical: 16),
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(12),
        ),
      ),
    );
  }

  static TextButtonThemeData _brandTextButton(ColorScheme colorScheme) {
    return TextButtonThemeData(
      style: TextButton.styleFrom(
        foregroundColor: primaryBrand,
        padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(8),
        ),
      ),
    );
  }

  static InputDecorationTheme _brandInputDecoration(ColorScheme colorScheme) {
    return InputDecorationTheme(
      filled: true,
      fillColor: colorScheme.surface,
      border: OutlineInputBorder(
        borderRadius: BorderRadius.circular(12),
        borderSide: BorderSide(color: colorScheme.outline),
      ),
      focusedBorder: OutlineInputBorder(
        borderRadius: BorderRadius.circular(12),
        borderSide: BorderSide(color: primaryBrand, width: 2),
      ),
      errorBorder: OutlineInputBorder(
        borderRadius: BorderRadius.circular(12),
        borderSide: BorderSide(color: colorScheme.error, width: 2),
      ),
      contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
    );
  }
}

// Custom theme extension
@immutable
class _BrandColors extends ThemeExtension<_BrandColors> {
  final Color primary;
  final Color secondary;
  final Color accent;
  final Color background;

  const _BrandColors({
    required this.primary,
    required this.secondary,
    required this.accent,
    required this.background,
  });

  @override
  _BrandColors copyWith({
    Color? primary,
    Color? secondary,
    Color? accent,
    Color? background,
  }) {
    return _BrandColors(
      primary: primary ?? this.primary,
      secondary: secondary ?? this.secondary,
      accent: accent ?? this.accent,
      background: background ?? this.background,
    );
  }

  @override
  _BrandColors lerp(ThemeExtension<_BrandColors>? other, double t) {
    if (other is! _BrandColors) return this;

    return _BrandColors(
      primary: Color.lerp(primary, other.primary, t)!,
      secondary: Color.lerp(secondary, other.secondary, t)!,
      accent: Color.lerp(accent, other.accent, t)!,
      background: Color.lerp(background, other.background, t)!,
    );
  }
}

Custom Theme System

Advanced theming with multiple variations.

1. Multi-Theme System

Switch between different visual themes.

enum AppThemeType { light, dark, blue, green, purple }

class ThemeManager extends ChangeNotifier {
  AppThemeType _currentTheme = AppThemeType.light;
  ThemeMode _themeMode = ThemeMode.system;

  AppThemeType get currentTheme => _currentTheme;
  ThemeMode get themeMode => _themeMode;

  void setTheme(AppThemeType theme) {
    _currentTheme = theme;
    notifyListeners();
  }

  void setThemeMode(ThemeMode mode) {
    _themeMode = mode;
    notifyListeners();
  }

  ThemeData get lightTheme => _getThemeData(AppThemeType.light, Brightness.light);
  ThemeData get darkTheme => _getThemeData(AppThemeType.dark, Brightness.dark);

  ThemeData _getThemeData(AppThemeType themeType, Brightness brightness) {
    switch (themeType) {
      case AppThemeType.light:
        return _buildLightTheme(brightness);
      case AppThemeType.dark:
        return _buildDarkTheme(brightness);
      case AppThemeType.blue:
        return _buildBlueTheme(brightness);
      case AppThemeType.green:
        return _buildGreenTheme(brightness);
      case AppThemeType.purple:
        return _buildPurpleTheme(brightness);
    }
  }

  ThemeData _buildLightTheme(Brightness brightness) {
    final colorScheme = ColorScheme.fromSeed(
      seedColor: Colors.blue,
      brightness: brightness,
    );

    return ThemeData(
      useMaterial3: true,
      colorScheme: colorScheme,
      appBarTheme: AppBarTheme(
        backgroundColor: colorScheme.surface,
        elevation: 0,
      ),
    );
  }

  ThemeData _buildBlueTheme(Brightness brightness) {
    final colorScheme = ColorScheme.fromSeed(
      seedColor: Colors.blue,
      brightness: brightness,
    );

    return ThemeData(
      useMaterial3: true,
      colorScheme: colorScheme,
      appBarTheme: AppBarTheme(
        backgroundColor: colorScheme.primaryContainer,
        foregroundColor: colorScheme.onPrimaryContainer,
      ),
    );
  }

  ThemeData _buildGreenTheme(Brightness brightness) {
    final colorScheme = ColorScheme.fromSeed(
      seedColor: Colors.green,
      brightness: brightness,
    );

    return ThemeData(
      useMaterial3: true,
      colorScheme: colorScheme,
      appBarTheme: AppBarTheme(
        backgroundColor: colorScheme.primary,
        foregroundColor: colorScheme.onPrimary,
      ),
    );
  }

  ThemeData _buildPurpleTheme(Brightness brightness) {
    final colorScheme = ColorScheme.fromSeed(
      seedColor: Colors.purple,
      brightness: brightness,
    );

    return ThemeData(
      useMaterial3: true,
      colorScheme: colorScheme,
      appBarTheme: AppBarTheme(
        backgroundColor: colorScheme.surfaceVariant,
        foregroundColor: colorScheme.onSurfaceVariant,
      ),
    );
  }
}

// Usage with Provider
class ThemeProvider extends StatelessWidget {
  final Widget child;

  const ThemeProvider({required this.child});

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => ThemeManager(),
      child: Consumer<ThemeManager>(
        builder: (context, themeManager, child) {
          return MaterialApp(
            theme: themeManager.lightTheme,
            darkTheme: themeManager.darkTheme,
            themeMode: themeManager.themeMode,
            child: child!,
          );
        },
        child: child,
      ),
    );
  }
}

2. Theme Switcher Widget

UI for switching themes.

class ThemeSwitcher extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<ThemeManager>(
      builder: (context, themeManager, child) {
        return PopupMenuButton<AppThemeType>(
          icon: Icon(Icons.palette),
          onSelected: (theme) => themeManager.setTheme(theme),
          itemBuilder: (context) => [
            PopupMenuItem(
              value: AppThemeType.light,
              child: Row(
                children: [
                  Icon(Icons.light_mode, color: Colors.amber),
                  SizedBox(width: 8),
                  Text('Light'),
                ],
              ),
            ),
            PopupMenuItem(
              value: AppThemeType.dark,
              child: Row(
                children: [
                  Icon(Icons.dark_mode, color: Colors.blueGrey),
                  SizedBox(width: 8),
                  Text('Dark'),
                ],
              ),
            ),
            PopupMenuItem(
              value: AppThemeType.blue,
              child: Row(
                children: [
                  Icon(Icons.circle, color: Colors.blue),
                  SizedBox(width: 8),
                  Text('Blue'),
                ],
              ),
            ),
            PopupMenuItem(
              value: AppThemeType.green,
              child: Row(
                children: [
                  Icon(Icons.circle, color: Colors.green),
                  SizedBox(width: 8),
                  Text('Green'),
                ],
              ),
            ),
            PopupMenuItem(
              value: AppThemeType.purple,
              child: Row(
                children: [
                  Icon(Icons.circle, color: Colors.purple),
                  SizedBox(width: 8),
                  Text('Purple'),
                ],
              ),
            ),
          ],
        );
      },
    );
  }
}

Responsive Theme Adaptation

Adapt themes for different screen sizes.

1. Responsive Theme Builder

Different themes for different screen sizes.

class ResponsiveTheme extends StatelessWidget {
  final Widget child;

  const ResponsiveTheme({required this.child});

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        ThemeData theme;

        if (constraints.maxWidth < 600) {
          // Mobile theme
          theme = _buildMobileTheme();
        } else if (constraints.maxWidth < 1200) {
          // Tablet theme
          theme = _buildTabletTheme();
        } else {
          // Desktop theme
          theme = _buildDesktopTheme();
        }

        return Theme(
          data: theme,
          child: child,
        );
      },
    );
  }

  ThemeData _buildMobileTheme() {
    return ThemeData(
      useMaterial3: true,
      textTheme: TextTheme(
        headlineLarge: TextStyle(fontSize: 24), // Smaller for mobile
        bodyLarge: TextStyle(fontSize: 16),
      ),
      elevatedButtonTheme: ElevatedButtonThemeData(
        style: ElevatedButton.styleFrom(
          padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), // Compact
        ),
      ),
    );
  }

  ThemeData _buildTabletTheme() {
    return ThemeData(
      useMaterial3: true,
      textTheme: TextTheme(
        headlineLarge: TextStyle(fontSize: 32), // Medium
        bodyLarge: TextStyle(fontSize: 18),
      ),
      elevatedButtonTheme: ElevatedButtonThemeData(
        style: ElevatedButton.styleFrom(
          padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
        ),
      ),
    );
  }

  ThemeData _buildDesktopTheme() {
    return ThemeData(
      useMaterial3: true,
      textTheme: TextTheme(
        headlineLarge: TextStyle(fontSize: 40), // Larger for desktop
        bodyLarge: TextStyle(fontSize: 20),
      ),
      elevatedButtonTheme: ElevatedButtonThemeData(
        style: ElevatedButton.styleFrom(
          padding: EdgeInsets.symmetric(horizontal: 32, vertical: 16), // Spacious
        ),
      ),
    );
  }
}

Theme Usage Examples

1. Access Theme in Widgets

Proper theme access patterns.

class ThemedWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final colorScheme = theme.colorScheme;
    final textTheme = theme.textTheme;

    return Container(
      color: colorScheme.primary,
      child: Text(
        'Themed Text',
        style: textTheme.headlineMedium?.copyWith(
          color: colorScheme.onPrimary,
        ),
      ),
    );
  }
}

// Using custom theme extensions
class CustomThemedWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final brandColors = Theme.of(context).extension<_BrandColors>();

    return Container(
      color: brandColors?.primary ?? Colors.blue,
      child: Text(
        'Brand Themed',
        style: TextStyle(color: brandColors != null ? Colors.white : null),
      ),
    );
  }
}

2. Theme Switching Animation

Smooth theme transitions.

class AnimatedThemeSwitch extends StatefulWidget {
  final Widget child;

  const AnimatedThemeSwitch({required this.child});

  @override
  _AnimatedThemeSwitchState createState() => _AnimatedThemeSwitchState();
}

class _AnimatedThemeSwitchState extends State<AnimatedThemeSwitch>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: Duration(milliseconds: 300),
      vsync: this,
    );
    _animation = CurvedAnimation(
      parent: _controller,
      curve: Curves.easeInOut,
    );
  }

  void switchTheme(ThemeData newTheme) {
    _controller.forward().then((_) {
      // Update theme
      _controller.reverse();
    });
  }

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

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _animation,
      builder: (context, child) {
        return Opacity(
          opacity: 1.0 - _animation.value.abs() * 0.3,
          child: child,
        );
      },
      child: widget.child,
    );
  }
}