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
-
Consistency
- Use consistent colors throughout the app
- Maintain uniform spacing and padding
- Follow platform-specific design guidelines
- Use semantic color names
-
Accessibility
- Ensure sufficient color contrast
- Use appropriate text sizes
- Support dynamic type scaling
- Consider color blindness
-
Performance
- Use const constructors for static styles
- Cache theme data when possible
- Avoid unnecessary rebuilds
- Use efficient styling methods
-
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!