Quick Summary:
Explore how to implement light and dark themes with custom colors in Flutter to enhance user experience and brand identity. From understanding the basics of Flutter theming to learning about the benefits and best practices, this comprehensive guide walks you through a step-by-step approach to creating a visually cohesive, user-friendly interface that thrives. Dive into this Flutter theme tutorial to master theming and design a dynamic, engaging app experience.
A theme is a generic styling element that represents the overall style, look, and feel of your application. When you wish to modify your Flutter app theme, say for eg. from Flutter Dark mode to Flutter Light mode or vice-versa, it is known as Flutter theming.
There are many articles available on Flutter theming, showing how to implement Flutter theme Dark Light in projects using the default App Theme and their default Colors attribute. But in the live project, we are required to add our custom Color for our app branding.
Before we dive into understanding how to create a Flutter custom theme, let us first shed some light on the key reasons to implement light and dark themes and some essential best practices to consider for Flutter theming.
In today’s modern app development, offering light and dark mode themes is not long just a trend; it’s a necessity. User expectations nowadays are increasingly evolving, and providing the freedom to switch between themes can indeed satisfy their needs and expectations. There are numerous benefits of implementing both light and dark themes in your Flutter application. Here are some reasons why providing both options is crucial:
User Comfort: Light mode Flutter theme might be ideal for well-lit environments as it offers a bright, clear interface that is easy to read in daylight. While dark mode theme can reduce eye strain in low-light settings, providing a more comfortable reading experience at night. Hence, by providing both options to users can help enhance user experience and comfort.
Enhanced Battery Life (For OLED Screens): When it comes to devices with OLED or AMOLED screens, dark mode consumes much less power as compared to light themes. Using dark Flutter themes can significantly reduce battery consumption on such displays. OLED pixels only light up when needed, so this can help users extend their battery life for prolonged usage.
Aesthetic Appeal: Well, the visual appeal of your application can be enhanced with both light and dark themes. By implementing light themes, you can get clean, minimalistic designs. While dark themes can provide a modern, sleek, and stylish aesthetic feel. Offering the choice of switching between dark and light themes can help meet different user preferences.
Aligning with Platform Standards: It is certain that iOS and Android support system-wide light and dark modes. Hence, it will be beneficial for your app to align with these system preferences to ensure your app feels native to the platform. Your app will easily adapt to users’ device settings without requiring manual theme switching.
Increased Accessibility: Users who need light sensitivity with certain visual impairments can choose the dark theme mode. On the other hand, the light mode can provide better clarity for users with low vision or difficulty reading text on darker backgrounds. By supporting both themes, you can increase accessibility for your application and serve a wider audience.
Personalized User Experience: When you provide your app users with both theme modes, it allows users to personalize their experience based on their preferences. Some users may feel more comfortable with one theme over the other. So you give them the option to switch from dark to light themes depending on the time of day or their environment. Implementing both themes can enable seamless interaction for your app users, as you give them better control over how they interact with your app.
To enhance the user experience of your Flutter app, it will be beneficial to implement a consistent and effective theme. Here are some Flutter theming best practices to ensure your app’s appearance is polished, appealing, and accessible:
It will indeed be fruitful to use a ThemeProvider or a similar mechanism to manage your app’s theme without any hassle. It allows your app users to easily switch between light and dark themes while allowing the developers to customize the appearance of your widgets.
Another best practice you should consider when customizing your Flutter app is to create a clear, consistent color palette for both light and dark themes. This would help ensure sustainable contrast and maintain a cohesive visual experience across your app.
By utilizing Flutter’s ThemeData class, you can customize different UI elements for certain outcomes. You can use it to define global attributes such as colors, text styles, button themes, and icon themes. Also, it will be feasible to set the theme for light mode and dark mode directly in the MaterialApp widget. So, your users can easily switch between light-dark themes based on their preferences.
One should go for clear, legible, and easy-to-read font sizes, weights, and styles for different text elements to ensure readability in both light and dark themes. It is essential to define custom typography settings, which makes it further easier to maintain consistent text styles across your Flutter app.
Your Flutter app should be easily accessible to everyone. Hence, you must ensure your color choices meet accessibility guidelines for users with visual impairments. It will be helpful to use high-contrast text against background colors and add enough spacing for ease of navigation. This would eventually help improve accessibility, enhance readability, and better user experience.
When you serve the users beyond their expectations, they are more likely to keep coming back for more. To create such an image in the user’s mind, you can provide an option in your Flutter app where users can manually switch between light and dark themes. You can either implement a toggle button or an automatic setting based on the device’s system theme preference to switch between these modes. State management solutions like Riverpod and Provider can help update the theme dynamically.
To successfully implement Flutter theming, you will have to take care of the following prerequisites:
Once you are ready to go ahead, execute the following steps consecutively for enabling custom Flutter theming.
Want to Implement a custom option to change the theme dynamically in your app?
Hire Flutter Developers from Bacancy to add or customize theme Color for your app branding
For creating a theme for light and dark mode, we use ThemeData class and customize colors and other properties based on needs. We have created one method for getting ThemeDate based on selected light/dark themes.
We give different values for scaffoldBackgroundColor, bodyColor, thumbColor, listTileTheme, and appBarTheme based on the selected theme.
ThemeData getAppTheme(BuildContext context, bool isDarkTheme) { return ThemeData( scaffoldBackgroundColor: isDarkTheme ? Colors.black : Colors.white, textTheme: Theme.of(context) .textTheme .copyWith( titleSmall: Theme.of(context).textTheme.titleSmall?.copyWith(fontSize: 11), ) .apply( bodyColor: isDarkTheme ? Colors.white : Colors.black, displayColor: Colors.grey, ), switchTheme: SwitchThemeData( thumbColor: MaterialStateProperty.all( isDarkTheme ? Colors.orange : Colors.purple), ), listTileTheme: ListTileThemeData( iconColor: isDarkTheme ? Colors.orange : Colors.purple), appBarTheme: AppBarTheme( backgroundColor: isDarkTheme ? Colors.black : Colors.white, iconTheme: IconThemeData(color: isDarkTheme ? Colors.white : Colors.black54)), ); }
We will use river-pod to manage the app theme state. We only want to store bool value to manage light or dark themes so we will use StateProvider.
We use river-pod in the project so we have to wrap MyApp with ProviderScope to access all providers though-out app. MyApp extends ConsumerWidget so we can get the WidgetRef object in the build method and access any river-pod using the ref variable. getAppTheme(context, ref.watch(appThemeProvider)) method listens to any change in the app theme and updates the app accordingly.
class MyApp extends ConsumerWidget { const MyApp({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { return MaterialApp( title: 'Flutter Light/Dark Theme', debugShowCheckedModeBanner: false, theme: getAppTheme(context, ref.watch(appThemeProvider)), home: const MyHomePage(), ); } }
ref.read(appThemeProvider.notifier).state = value. We are updating the theme state in appThemeProvider when the switch state is changed from light/dark mood.
Switch( activeColor: Colors.orange, onChanged: (value) { ref.read(appThemeProvider.notifier).state = value; }, value: isDarkMode )
It works fine as far as it will show the same color in all icons and texts. If we want to use different colors on icons, we have to create an extension for the Theme. Create a class and extend with ThemeExtension and add necessary fields that you want to customize.
class AppColors extends ThemeExtension { final Color? color1; final Color? color2; final Color? color3; const AppColors({ required this.color1, required this.color2, required this.color3, }); @override AppColors copyWith({ Color? color1, Color? color2, Color? color3, }) { return AppColors( color1: color1 ?? this.color1, color2: color2 ?? this.color2, color3: color3 ?? this.color3, ); } @override AppColors lerp(ThemeExtension ? other, double t) { if (other is! AppColors) { return this; } return AppColors( color1: Color.lerp(color1, other.color1, t), color2: Color.lerp(color2, other.color2, t), color3: Color.lerp(color3, other.color3, t), ); } }
Now add this extension attribute in ThemeData in our create method getAppTheme and define the theme according to colors.
extensions: >[ AppColors( color1: isDarkTheme ? Colors.blue : Colors.blueGrey, color2: isDarkTheme ? Colors.pink : Colors.pinkAccent, color3: isDarkTheme ? Colors.yellow : Colors.limeAccent, ),
Create another extension function which we use to access custom color easily.
We can access these colors in the widget simply using colors(context).color1. If we do not specify the Icon color, it will fetch the color from listTileTheme.
ListTile( leading: Icon(Icons.chat_outlined, color: colors(context).color3), title: Text( "Help Center", style: Theme.of(context).textTheme.titleSmall), ), ListTile( leading: const Icon(Icons.notifications), title: Text("Notification", style: Theme.of(context).textTheme.titleSmall), ),
Here is the code for theme class:
import 'package:flutter/material.dart'; AppColors colors(context) => Theme.of(context).extension ()!; ThemeData getAppTheme(BuildContext context, bool isDarkTheme) { return ThemeData( extensions: >[ AppColors( color1: isDarkTheme ? Colors.blue : Colors.green, color2: isDarkTheme ? Colors.pink : Colors.blue, color3: isDarkTheme ? Colors.yellow : Colors.red, ), ], scaffoldBackgroundColor: isDarkTheme ? Colors.black : Colors.white, textTheme: Theme.of(context) .textTheme .copyWith( titleSmall: Theme.of(context).textTheme.titleSmall?.copyWith(fontSize: 12), ) .apply( bodyColor: isDarkTheme ? Colors.white : Colors.black, displayColor: Colors.grey, ), switchTheme: SwitchThemeData( thumbColor: MaterialStateProperty.all( isDarkTheme ? Colors.orange : Colors.purple), ), listTileTheme: ListTileThemeData( iconColor: isDarkTheme ? Colors.orange : Colors.purple), appBarTheme: AppBarTheme( backgroundColor: isDarkTheme ? Colors.black : Colors.white, iconTheme: IconThemeData(color: isDarkTheme ? Colors.white : Colors.black54)), ); } @immutable class AppColors extends ThemeExtension { final Color? color1; final Color? color2; final Color? color3; const AppColors({ required this.color1, required this.color2, required this.color3, }); @override AppColors copyWith({ Color? color1, Color? color2, Color? color3, }) { return AppColors( color1: color1 ?? this.color1, color2: color2 ?? this.color2, color3: color3 ?? this.color3, ); } @override AppColors lerp(ThemeExtension ? other, double t) { if (other is! AppColors) { return this; } return AppColors( color1: Color.lerp(color1, other.color1, t), color2: Color.lerp(color2, other.color2, t), color3: Color.lerp(color3, other.color3, t), ); } }
Here is code of our main screen:
import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:light_dark_mode/provider%20/app_theme_provider.dart'; import 'package:light_dark_mode/utils/app_theme.dart'; void main() { runApp(const ProviderScope(child: MyApp())); } class MyApp extends ConsumerWidget { const MyApp({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { return MaterialApp( title: 'Flutter Light/Dark Theme', debugShowCheckedModeBanner: false, theme: getAppTheme(context, ref.watch(appThemeProvider)), home: const MyHomePage(), ); } } class MyHomePage extends ConsumerWidget { const MyHomePage({Key? key}) : super(key: key); @override Widget build(BuildContext context, WidgetRef ref) { var isDarkMode = ref.watch(appThemeProvider); return Scaffold( appBar: AppBar( elevation: 0, leading: const Icon(Icons.arrow_back_ios_sharp), actions: const [ Padding( padding: EdgeInsets.symmetric(horizontal: 15.0), child: Icon(Icons.add_circle_outline), ) ], ), body: Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: ListView( children: [ CircleAvatar( radius: 60, backgroundColor: Colors.grey, child: Padding( padding: const EdgeInsets.all(1), // Border radius child: ClipRRect( borderRadius: BorderRadius.circular(60), child: Image.asset( "assets/ic_profile.jpeg", fit: BoxFit.fill, width: 120, height: 120, )), ), ), Container( margin: const EdgeInsets.only(top: 10, bottom: 60), alignment: Alignment.center, child: Text( "Testing User", style: Theme.of(context).textTheme.titleLarge, ), ), ListTile( leading: Icon(isDarkMode ? Icons.brightness_3 : Icons.sunny), title: Text( isDarkMode ? "Dark mode" : "Light mode", style: Theme.of(context).textTheme.titleSmall, ), trailing: Consumer(builder: (context, ref, child) { return Transform.scale( scale: 0.7, child: Switch( activeColor: Colors.orange, onChanged: (value) { ref.read(appThemeProvider.notifier).state = value; }, value: isDarkMode, ), ); }), ), ListTile( leading: Icon(Icons.grid_on_sharp, color: colors(context).color1,), title: Text( "Story", style: Theme.of(context).textTheme.titleSmall, ), ), ListTile( leading: Icon(Icons.settings, color: colors(context).color2), title: Text("Settings and Privacy", style: Theme.of(context).textTheme.titleSmall), ), ListTile( leading: Icon(Icons.chat_outlined, color: colors(context).color3), title: Text( "Help Center", style: Theme.of(context).textTheme.titleSmall, ), ), ListTile( leading: const Icon(Icons.notifications), title: Text( "Notification", style: Theme.of(context).textTheme.titleSmall, ), ), ], ), ), ); } }
You can find full code here: GitHub Repository.
In conclusion, designing custom Flutter theming can help create a user-friendly, interactive app that feels polished, visually appealing, and accessible across different devices and platforms. You can implement light and dark themes with custom colors within your Flutter app to give your users a top-quality experience. It will be possible to enhance user satisfaction by delivering the comfort, better efficiency, and personalization options they seek.
As Flutter offers you the flexibility to customize your app theme based on your brand identity and user needs, you can create an app interface that truly stands out. In case, you need expert guidance on building stunning, user-friendly Flutter themes, consider connecting with a reliable Flutter app development company to create a seamless, engaging interface.
Your Success Is Guaranteed !
We accelerate the release of digital product and guaranteed their success
We Use Slack, Jira & GitHub for Accurate Deployment and Effective Communication.