Theming
The theme plays a crucial role in defining the visual properties of an app, such as colors, typography, and other styling attributes. Inconsistencies within the theme can result in poor user experiences and potentially distort the intended design. Fortunately, Flutter offers a great design system that enables us to develop reusable and structured code that ensures a consistent theme.
Use ThemeData
By using ThemeData
, widgets will inherit their styles automatically which is especially important for managing light/dark themes as it allows referencing the same token in widgets and removes the need for conditional logic.
The above widget might match the design and visually look fine, but if you continue this structure, any design updates could result in you changing a bunch of files instead of just one.
Now, we are using ThemeData
to get the ColorScheme
and TextTheme
so that any design update will automatically reference the correct value.
Avoid Conditional Logic
It’s generally recommended to steer clear of using conditional logic in UI for theming. This approach can complicate testing and make the code less readable. By leveraging Flutter’s built-in design system, your app can have cleaner, more maintainable code that ensures consistent styling.
Typography
Implementing typography is generally straightforward, but it’s also easy to make mistakes, such as forgetting to adjust TextStyle
attributes like height
or resorting to hardcoded values instead of utilizing TextTheme
.
Let’s break down typography into three sections:
Importing Fonts
To keep things organized, fonts are generally stored in an assets
folder:
Then declared in the pubspec.yaml
file:
At this point, the font is imported and ready to use. However, to ensure type safety, we recommend using flutter_gen to generate code for our font. Here’s an example what that generated code might look like:
Custom Text Styles
Whether importing a custom font or using the default one, it’s a good idea to create a custom class for your text styles to maintain consistency and simplify updates across your app. Let’s take a look at this example:
With this setup, any updates to the style are centralized which reduces the need to find it in multiple locations.
TextTheme
The last step to implement typography is to update the TextTheme. Both TextTheme
and Custom Text Styles serve important roles but cater to different aspects of text styling. The benefit of using TextTheme
is the seamless integration into ThemeData
that allows for consistent application of text styles across widgets that use the current theme.
Here’s a basic example:
Widgets can now reference the text style through ThemeData
:
Colors
Based on the Material 3 color system, Flutter offers a ColorScheme
class that includes a set of 45 colors, which can be utilized to configure the color properties of most components. Instead of using an absolute color such as Colors.blue
or Color(0xFF42A5F5)
, we recommend using Theme.of
to access the local ColorScheme
. This ColorScheme
can be configured within ThemeData
using a custom colors class such as AppColors
.
Custom Colors
Whether using default Material Colors or custom ones, we recommend creating a custom class for your colors for easy access and consistency.
ColorScheme
Once we have a custom class for colors, update the ColorScheme
:
Now widgets referencing those tokens will use the colors defined in AppColors
, ensuring consistency across the app.
Component Theming
Flutter provides a variety of Material component widgets that implement the Material 3 design specification.
Material components primarily rely on the colorScheme
and textTheme
for their styling, but each widget also has its own customizable theme as part of ThemeData
.
For instance, if we want all FilledButton
widgets to have a minimum width of 72
, we can use FilledButtonThemeData
:
We recommend leveraging component theming to customize widgets whenever possible, rather than applying customizations directly within each widget’s code. Centralizing these customizations in ThemeData
will help your widgets avoid conditional logic and ensure theming consistency in your app.
Spacing
Spacing is one of the most important aspects of theming and design. If the UI is created without intentional spacing, users are likely to have a bad experience as the content of the app may be overwhelming and hard to navigate. Good designs will generally follow a spacing system using a base unit to simplify the creation of page layouts and UI.
Just as custom text styles and custom colors can be centralized in a class, spacing can also follow this setup:
Now, anytime spacing needs to be added to a widget, you can reference this class to ensure consistency and avoid hardcoded values.