Back to Posts

Flutter Theming and Styling

11 min read

Creating a consistent and beautiful design is crucial for any Flutter app. This guide will walk you through everything you need to know about theming and styling in Flutter.

Material Theme Basics

1. Default Material Theme

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.blue,
        brightness: Brightness.light,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(),
    );
  }
}

2. Custom Material Theme

class CustomTheme {
  static ThemeData get lightTheme {
    return ThemeData(
      primaryColor: Color(0xFF6200EE),
      accentColor: Color(0xFF03DAC6),
      scaffoldBackgroundColor: Colors.white,
      fontFamily: 'Roboto',
      textTheme: TextTheme(
        headline1: TextStyle(fontSize: 72.0, fontWeight: FontWeight.bold),
        headline6: TextStyle(fontSize: 36.0, fontStyle: FontStyle.italic),
        bodyText2: TextStyle(fontSize: 14.0, fontFamily: 'Hind'),
      ),
      buttonTheme: ButtonThemeData(
        buttonColor: Color(0xFF6200EE),
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(8.0),
        ),
      ),
    );
  }
}

Cupertino Theme

1. Default Cupertino Theme

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CupertinoApp(
      theme: CupertinoThemeData(
        primaryColor: CupertinoColors.systemBlue,
        brightness: Brightness.light,
        textTheme: CupertinoTextThemeData(
          primaryColor: CupertinoColors.black,
        ),
      ),
      home: MyHomePage(),
    );
  }
}

2. Custom Cupertino Theme

class CustomCupertinoTheme {
  static CupertinoThemeData get theme {
    return CupertinoThemeData(
      primaryColor: CupertinoColors.systemPurple,
      brightness: Brightness.light,
      scaffoldBackgroundColor: CupertinoColors.systemBackground,
      textTheme: CupertinoTextThemeData(
        primaryColor: CupertinoColors.label,
        textStyle: TextStyle(
          fontFamily: 'SF Pro Text',
          fontSize: 17.0,
        ),
      ),
    );
  }
}

Theme Extensions

1. Creating Custom Theme Extensions

class CustomColors extends ThemeExtension<CustomColors> {
  final Color success;
  final Color warning;
  final Color error;

  const CustomColors({
    required this.success,
    required this.warning,
    required this.error,
  });

  @override
  ThemeExtension<CustomColors> copyWith({
    Color? success,
    Color? warning,
    Color? error,
  }) {
    return CustomColors(
      success: success ?? this.success,
      warning: warning ?? this.warning,
      error: error ?? this.error,
    );
  }

  @override
  ThemeExtension<CustomColors> lerp(ThemeExtension<CustomColors>? other, double t) {
    if (other is! CustomColors) return this;
    return CustomColors(
      success: Color.lerp(success, other.success, t)!,
      warning: Color.lerp(warning, other.warning, t)!,
      error: Color.lerp(error, other.error, t)!,
    );
  }
}

2. Using Theme Extensions

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        extensions: <ThemeExtension<dynamic>>[
          CustomColors(
            success: Colors.green,
            warning: Colors.orange,
            error: Colors.red,
          ),
        ],
      ),
      home: MyHomePage(),
    );
  }
}

// Usage
final customColors = Theme.of(context).extension<CustomColors>()!;
Container(
  color: customColors.success,
  child: Text('Success'),
);

Dark Theme

1. Implementing Dark Theme

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        brightness: Brightness.light,
        primarySwatch: Colors.blue,
      ),
      darkTheme: ThemeData(
        brightness: Brightness.dark,
        primarySwatch: Colors.blue,
        scaffoldBackgroundColor: Colors.grey[900],
        cardColor: Colors.grey[800],
      ),
      themeMode: ThemeMode.system, // or ThemeMode.light, ThemeMode.dark
      home: MyHomePage(),
    );
  }
}

2. Dynamic Theme Switching

class ThemeProvider extends ChangeNotifier {
  ThemeMode _themeMode = ThemeMode.system;

  ThemeMode get themeMode => _themeMode;

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

// Usage
Consumer<ThemeProvider>(
  builder: (context, themeProvider, child) {
    return MaterialApp(
      themeMode: themeProvider.themeMode,
      theme: ThemeData.light(),
      darkTheme: ThemeData.dark(),
      home: MyHomePage(),
    );
  },
);

Custom Widget Styling

1. Creating Reusable Styles

class AppStyles {
  static const TextStyle heading = TextStyle(
    fontSize: 24.0,
    fontWeight: FontWeight.bold,
    color: Colors.black,
  );

  static const TextStyle body = TextStyle(
    fontSize: 16.0,
    color: Colors.black87,
  );

  static const BoxDecoration cardDecoration = BoxDecoration(
    color: Colors.white,
    borderRadius: BorderRadius.all(Radius.circular(8.0)),
    boxShadow: [
      BoxShadow(
        color: Colors.black12,
        blurRadius: 4.0,
        offset: Offset(0, 2),
      ),
    ],
  );
}

2. Using Custom Styles

class StyledCard extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: AppStyles.cardDecoration,
      child: Column(
        children: [
          Text(
            'Card Title',
            style: AppStyles.heading,
          ),
          Text(
            'Card content goes here',
            style: AppStyles.body,
          ),
        ],
      ),
    );
  }
}

Best Practices

  1. Consistency

    • Use consistent colors throughout the app
    • Maintain uniform spacing and padding
    • Follow platform-specific design guidelines
    • Use semantic color names
  2. Accessibility

    • Ensure sufficient color contrast
    • Use appropriate text sizes
    • Support dynamic type scaling
    • Consider color blindness
  3. Performance

    • Use const constructors for static styles
    • Cache theme data when possible
    • Avoid unnecessary rebuilds
    • Use efficient styling methods
  4. Maintenance

    • Centralize theme configuration
    • Use meaningful variable names
    • Document theme decisions
    • Keep styles organized

Example: Complete Theme Implementation

Here's a complete example of a themed Flutter app:

class ThemedApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Themed App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        brightness: Brightness.light,
        visualDensity: VisualDensity.adaptivePlatformDensity,
        textTheme: TextTheme(
          headline1: TextStyle(fontSize: 72.0, fontWeight: FontWeight.bold),
          headline6: TextStyle(fontSize: 36.0, fontStyle: FontStyle.italic),
          bodyText2: TextStyle(fontSize: 14.0, fontFamily: 'Hind'),
        ),
        extensions: <ThemeExtension<dynamic>>[
          CustomColors(
            success: Colors.green,
            warning: Colors.orange,
            error: Colors.red,
          ),
        ],
      ),
      darkTheme: ThemeData(
        brightness: Brightness.dark,
        primarySwatch: Colors.blue,
        scaffoldBackgroundColor: Colors.grey[900],
        cardColor: Colors.grey[800],
        textTheme: TextTheme(
          headline1: TextStyle(fontSize: 72.0, fontWeight: FontWeight.bold, color: Colors.white),
          headline6: TextStyle(fontSize: 36.0, fontStyle: FontStyle.italic, color: Colors.white),
          bodyText2: TextStyle(fontSize: 14.0, fontFamily: 'Hind', color: Colors.white70),
        ),
        extensions: <ThemeExtension<dynamic>>[
          CustomColors(
            success: Colors.green[300]!,
            warning: Colors.orange[300]!,
            error: Colors.red[300]!,
          ),
        ],
      ),
      themeMode: ThemeMode.system,
      home: MyHomePage(),
    );
  }
}

Common Issues and Solutions

1. Theme Not Applying

// Make sure to wrap your app with the correct theme provider
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => ThemeProvider(),
      child: Consumer<ThemeProvider>(
        builder: (context, themeProvider, child) {
          return MaterialApp(
            themeMode: themeProvider.themeMode,
            // ... rest of the configuration
          );
        },
      ),
    );
  }
}

2. Inconsistent Styling

// Create a centralized style guide
class StyleGuide {
  static const double spacing = 16.0;
  static const double borderRadius = 8.0;
  static const double iconSize = 24.0;
  
  static const EdgeInsets padding = EdgeInsets.all(spacing);
  static const EdgeInsets margin = EdgeInsets.all(spacing);
  
  static const BorderRadius radius = BorderRadius.all(Radius.circular(borderRadius));
}

Conclusion

Flutter theming and styling involves:

  • Understanding Material and Cupertino themes
  • Creating custom themes and styles
  • Implementing dark mode
  • Following best practices

Remember to:

  • Keep your design consistent
  • Consider accessibility
  • Optimize for performance
  • Maintain your code

With these techniques, you can create beautiful and consistent Flutter apps that provide an excellent user experience!