Theme Your Flutter App: A Guide to ThemeData and ColorScheme

Nikhithsunil
5 min readFeb 28, 2024

Before diving in I need to tell you something. Many articles are available on this topic in the medium and other sources, so what is the necessity of this article?

In this article, I plan to focus on only the key points of the ThemeData widget and the most used parameters from my development experience, and you get a brief explanation about how each parameter takes action on your application.

Are you curious? continue reading 🤗.

Key Benefits of using ThemeData

  • Maintain a unified look and feel: Define a single ThemeData object that encapsulates your app's color palette, fonts, shapes, and other visual elements. Apply this theme consistently across all screens, ensuring a cohesive and recognizable brand identity.
  • Create variations for different themes: Define multiple ThemeData objects for light and dark modes, app sections, or user preferences.
  • Define themes once, and use them everywhere: Instead of manually setting visual styles for individual widgets, apply the appropriate ThemeData ones in your app. This reduces code duplication and simplifies maintenance.
  • Centralized control and updates: Make changes to the ThemeData object and those changes automatically propagate throughout your app, ensuring consistency and reducing the need for repetitive edits.
  • Create accessible variants: Build separate ThemeData objects for users with specific accessibility needs, such as high-contrast themes for visually impaired users.

So, now you are familiar with ThemeData how it helps you, then how to implement this in your app? please stick with me 😊.

Here is a small guide to implementing a basic Theme in a Flutter app for Dark and Light themes.

Creating Global Class

The first step is to create a global class for managing ThemeData within your application. This contains a method to create different instances ThemeData with ColorSheme .

class GlobalThemData {
static ThemeData themeData(ColorScheme colorScheme, Color focusColor) {
return ThemeData(colorScheme: colorScheme, focusColor: focusColor);
}
}

focusColor : This color is used by widgets like TextFields and TextFormField to indicate the widget has a primary focus.

ColorSheme : A set of 30 colors based on the Material spec that can be used to configure the color properties of most components.

We can discuss ColorSheme in more detail later in this article.

Now we can create additional public variables that can be accessed directly from GlobalThemData class.

lightColorScheme: holds a ColorSheme for light theme.

darkColorScheme: holds a ColorSheme for dark theme.

lightThemeData: holds a ThemeData for light theme.

darkThemeData: holds a ThemeData for dark theme.

class GlobalThemData {
static final Color _lightFocusColor = Colors.black.withOpacity(0.12);
static final Color _darkFocusColor = Colors.white.withOpacity(0.12);
  static ThemeData lightThemeData = themeData(lightColorScheme, _lightFocusColor);

static ThemeData darkThemeData = themeData(darkColorScheme, _darkFocusColor);
static ThemeData themeData(ColorScheme colorScheme, Color focusColor) {
return ThemeData(colorScheme: colorScheme, focusColor: focusColor);
}
static const ColorScheme lightColorScheme = ColorScheme(); static const ColorScheme darkColorScheme = ColorScheme();
}

If you are coding along with me you should probably get a required parameter error warning on ColorSheme() .

we can fix this in the next step.

ColorSheme

The colors in ColorSheme come in pairs; the first is the color itself, and the second is the color that may be used on that color, such as text and other elements.

these 10 colors are mandatory for creating a ColorSheme for Flutter ThemData.Values for each color are optional.

primary : This is the most used color in the application

onPrimary : This color is used to color the elements on top of the primary color such as text, icons, etc.

secondary : This defines a secondary color, often used for less prominent elements like filter chips, toggle buttons, or background elements that need to stand out from the primary color but not overpower it.

onSecondary : This color is used to color the elements on top of the secondary color.

error : This is the color used for error messages or alerts, like a flashing red light to indicate a problem.

onError : This is the text color that goes well on the error color, like white text on a red sign for easy reading.

background : The primary background color for your entire application. Think of this as the canvas upon which all other UI elements are placed.

onBackground : This color is used to color the elements on top of the background color.

surface : Used as the base color for elevated UI elements like cards, sheets, dialogs, etc.

onSurface : use to color the elements on top of the surface color.

So, we can set our lightColorScheme and darkColorScheme variable as follows.

static const ColorScheme lightColorScheme = ColorScheme(
primary: Color(0xFFB93C5D),
onPrimary: Colors.black,
secondary: Color(0xFFEFF3F3),
onSecondary: Color(0xFF322942),
error: Colors.redAccent,
onError: Colors.white,
background: Color(0xFFE6EBEB),
onBackground: Colors.white,
surface: Color(0xFFFAFBFB),
onSurface: Color(0xFF241E30),
brightness: Brightness.light,
);
  static const ColorScheme darkColorScheme = ColorScheme(
primary: Color(0xFFFF8383),
secondary: Color(0xFF4D1F7C),
background: Color(0xFF241E30),
surface: Color(0xFF1F1929),
onBackground: Color(0x0DFFFFFF),
error: Colors.redAccent,
onError: Colors.white,
onPrimary: Colors.white,
onSecondary: Colors.white,
onSurface: Colors.white,
brightness: Brightness.dark,
);

So as of now, we set a ColorScheme for light and dark themes, now how we use this in our ThemeData ?

Creating ThemeData

we need to modify our themeData method in GlobalThemeData to build the ThemeData with the appropriate value ColourScheme that is going to pass to it.

static ThemeData themeData(ColorScheme colorScheme, Color focusColor) {
return ThemeData(
colorScheme: colorScheme,
canvasColor: colorScheme.background,
scaffoldBackgroundColor: colorScheme.background,
highlightColor: Colors.transparent,
focusColor: focusColor
);
}
  • canvasColor: This is the background color for the entire screen or app window. It defines the base color upon which all other UI elements are placed.
  • scaffoldBackgroundColor: This specifically defines the background color of the scaffold itself, encompassing the app bar, body content area, and bottom navigation bar (if present).
  • highlightColor: This property defines the color that is displayed briefly when a user taps and holds on a widget. It provides visual feedback to the user that the interaction has been registered.
  • focusColor: This property defines the color used to visually indicate which element currently has focus, meaning it's the element that will receive keyboard input. This can be useful for highlighting the currently active element, drawing the user's attention to it.

These are just samples and there are a bunch of other options available on ThemeData that you need to explore.

Therefore our final GlobalThemeData class should look like this,

class GlobalThemData {
static final Color _lightFocusColor = Colors.black.withOpacity(0.12);
static final Color _darkFocusColor = Colors.white.withOpacity(0.12);
  static ThemeData lightThemeData =
themeData(lightColorScheme, _lightFocusColor);
static ThemeData darkThemeData = themeData(darkColorScheme, _darkFocusColor); static ThemeData themeData(ColorScheme colorScheme, Color focusColor) {
return ThemeData(
colorScheme: colorScheme,
canvasColor: colorScheme.background,
scaffoldBackgroundColor: colorScheme.background,
highlightColor: Colors.transparent,
focusColor: focusColor
);
}
static const ColorScheme lightColorScheme = ColorScheme(
primary: Color(0xFFB93C5D),
onPrimary: Colors.black,
secondary: Color(0xFFEFF3F3),
onSecondary: Color(0xFF322942),
error: Colors.redAccent,
onError: Colors.white,
background: Color(0xFFE6EBEB),
onBackground: Colors.white,
surface: Color(0xFFFAFBFB),
onSurface: Color(0xFF241E30),
brightness: Brightness.light,
);
static const ColorScheme darkColorScheme = ColorScheme(
primary: Color(0xFFFF8383),
secondary: Color(0xFF4D1F7C),
background: Color(0xFF241E30),
surface: Color(0xFF1F1929),
onBackground: Color(0x0DFFFFFF),
error: Colors.redAccent,
onError: Colors.white,
onPrimary: Colors.white,
onSecondary: Colors.white,
onSurface: Colors.white,
brightness: Brightness.dark,
);
}

Yep! We just created a beautiful Theme for our application. now what?

Setting ThemeData

sets the desired themes in MaterialApp .

class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
themeMode: ThemeMode.light, //or ThemeMode.dark
theme: GlobalThemData.lightThemeData,
darkTheme: GlobalThemData.darkThemeData,
home: const ShowCaseHome(),
);
}
}

This will give a default light theme to your application, you can change the mode to dark. You can explore the power of InheritedWidget or Provider for switching the dynamically. It is out of the scope of this article, if you need we can discuss it in detail in a future article.

Hope you get some valuable information, Thank you for reading 🤗.

--

--