ColorScheme in Flutter

Master color design and theming with ColorScheme.

Color is one of the most powerful tools in app design. It communicates mood, brand identity, and hierarchy, while enhancing usability and accessibility. In Flutter, the ColorScheme class provides a modern, consistent way to define the colors for your entire application. It ensures that your app’s color palette is coherent, maintainable, and aligned with Material Design principles.

In this post, we will explore ColorScheme in detail, covering its purpose, configuration, integration with ThemeData, dynamic updates, dark mode, accessibility considerations, and best practices for creating professional, visually appealing Flutter apps.


Understanding ColorScheme

ColorScheme is a class in Flutter that defines a set of 13 colors that can be used throughout your app. It is designed to replace the older method of defining colors individually in ThemeData.

Key benefits of using ColorScheme include:

  • Consistency: Provides a standardized set of colors for your app.
  • Maintainability: Changing one color in the scheme updates all dependent widgets automatically.
  • Alignment with Material Design: Follows Material Design guidelines for primary, secondary, and neutral colors.

Flutter provides two main ways to define color schemes:

  1. Predefined schemes: ColorScheme.light() and ColorScheme.dark() for light and dark themes.
  2. Custom schemes: Defining all colors manually for full control.

ColorScheme Properties

ColorScheme includes a wide range of properties to cover all app elements. The primary properties include:

  • primary: The primary color of your app, used for AppBar, buttons, and highlights.
  • onPrimary: The color for text and icons displayed on top of primary color.
  • secondary: Used for floating action buttons, selection controls, and accent elements.
  • onSecondary: Text and icon color on secondary backgrounds.
  • background: The default background color of screens.
  • onBackground: Text color on background surfaces.
  • surface: Background color for cards, dialogs, and sheets.
  • onSurface: Text and icon color on surface elements.
  • error: Color representing errors or alerts.
  • onError: Text and icon color on error backgrounds.

Example of a simple custom color scheme:

ColorScheme myColorScheme = ColorScheme.light(
  primary: Colors.blue,
  onPrimary: Colors.white,
  secondary: Colors.orange,
  onSecondary: Colors.white,
  background: Colors.grey[50]!,
  onBackground: Colors.black,
  surface: Colors.white,
  onSurface: Colors.black87,
  error: Colors.red,
  onError: Colors.white,
);

Integrating ColorScheme with ThemeData

Flutter allows you to create a ThemeData object from a ColorScheme using ThemeData.from(). This ensures that all widgets respecting the theme use the defined color scheme automatically.

Example:

theme: ThemeData.from(colorScheme: myColorScheme)

This creates a ThemeData object where the colors for AppBar, buttons, cards, and other widgets are derived from your ColorScheme.


Primary and Secondary Colors

The primary and secondary colors are the most commonly used colors in your app.

  • Primary color: Defines the main color identity of your app. Appears in AppBars, buttons, and highlights.
  • Secondary color: Used for accents and interactive elements like floating action buttons or selection controls.

Example:

ColorScheme colorScheme = ColorScheme.light(
  primary: Colors.blue,
  secondary: Colors.orange,
);
  • The AppBar will use primary.
  • FloatingActionButton and other accents will use secondary.

Background and Surface Colors

background and surface colors are used for different layers of your app:

  • Background: Default scaffold background, main app background.
  • Surface: Used for cards, dialogs, and sheets that appear above the background.

Example:

ColorScheme colorScheme = ColorScheme.light(
  background: Colors.grey[50]!,
  surface: Colors.white,
)

This provides a subtle hierarchy between the main background and elevated surfaces, enhancing readability and visual clarity.


Error Colors

error and onError define how errors are displayed in your app:

  • error: Background color for error messages, inputs, or alerts.
  • onError: Text color for content displayed on error backgrounds.

Example:

ColorScheme colorScheme = ColorScheme.light(
  error: Colors.red,
  onError: Colors.white,
)

Flutter widgets like TextField, SnackBar, and AlertDialog automatically use these colors when showing errors.


On Colors

The onColors define the color of text and icons displayed on top of other colors. These include:

  • onPrimary: Text/icons on primary background.
  • onSecondary: Text/icons on secondary background.
  • onBackground: Text/icons on the main background.
  • onSurface: Text/icons on surface elements.
  • onError: Text/icons on error backgrounds.

Example:

ColorScheme colorScheme = ColorScheme.light(
  primary: Colors.blue,
  onPrimary: Colors.white,
  secondary: Colors.orange,
  onSecondary: Colors.black,
)

Using onColors ensures readability and accessibility. For example, a white icon on a dark primary background is easier to see than a black icon.


Dark Mode ColorScheme

Flutter supports dark themes by providing a ColorScheme.dark() constructor. You can define a dark color scheme to ensure your app is readable and visually appealing in low-light environments.

Example:

ColorScheme darkScheme = ColorScheme.dark(
  primary: Colors.deepPurple,
  secondary: Colors.amber,
  background: Colors.black,
  surface: Colors.grey[850]!,
)

Integrating with ThemeData:

theme: ThemeData.from(colorScheme: myColorScheme),
darkTheme: ThemeData.from(colorScheme: darkScheme)

Flutter automatically switches between light and dark color schemes based on system settings or user preferences.


Dynamic Color Schemes

Flutter allows dynamically updating your color scheme at runtime. This is useful for user-selected themes, seasonal variations, or branding updates.

Example:

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  bool isBlueTheme = true;

  @override
  Widget build(BuildContext context) {
ColorScheme colorScheme = isBlueTheme
    ? ColorScheme.light(primary: Colors.blue, secondary: Colors.orange)
    : ColorScheme.light(primary: Colors.green, secondary: Colors.pink);
return MaterialApp(
  theme: ThemeData.from(colorScheme: colorScheme),
  home: Scaffold(
    appBar: AppBar(title: Text('Dynamic ColorScheme')),
    body: Center(
      child: ElevatedButton(
        onPressed: () {
          setState(() {
            isBlueTheme = !isBlueTheme;
          });
        },
        child: Text('Switch Theme'),
      ),
    ),
  ),
);
} }

Clicking the button dynamically switches the primary and secondary colors throughout the app.


Accessibility Considerations

Color choice impacts accessibility. Using ColorScheme makes it easier to maintain readable contrasts:

  • Ensure sufficient contrast between text and background colors (onBackground vs background).
  • Use onPrimary and onSecondary for text and icons to maintain readability.
  • Avoid relying solely on color to convey information; combine with text or icons.

Flutter also supports the highContrast property in dark mode themes for improved accessibility.


Combining ColorScheme with Widgets

Many Flutter widgets automatically adopt colors from the ColorScheme:

  • AppBar: Uses primary and onPrimary.
  • FloatingActionButton: Uses secondary and onSecondary.
  • Buttons: ElevatedButton, TextButton, and OutlinedButton adopt colors from the scheme.
  • SnackBars, Chips, and Cards: Use surface and onSurface by default.

Example:

Scaffold(
  appBar: AppBar(title: Text('ColorScheme Example')),
  floatingActionButton: FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
), body: Center(
child: ElevatedButton(
  onPressed: () {},
  child: Text('Press Me'),
),
), )

All these widgets automatically use the colors defined in the ColorScheme.


Customizing Individual Widgets

If needed, you can override the color scheme locally for specific widgets using their style property.

Example:

ElevatedButton(
  style: ElevatedButton.styleFrom(
primary: Colors.green, // overrides secondary
onPrimary: Colors.white,
), onPressed: () {}, child: Text('Custom Button'), )

However, using ColorScheme ensures global consistency, which is generally preferred.


Best Practices for ColorScheme

  1. Define a comprehensive scheme: Cover all primary, secondary, background, surface, and error colors.
  2. Use onColors correctly: Ensure text and icons remain readable on colored backgrounds.
  3. Support light and dark themes: Create separate color schemes for both modes.
  4. Use dynamic themes sparingly: Only allow users to switch colors if it adds meaningful customization.
  5. Test accessibility: Check contrast ratios for text, icons, and interactive elements.
  6. Leverage Material Design guidelines: Use recommended color combinations for a modern, professional look.

Common Mistakes

  • Using only primary and secondary: Ignoring background and surface colors can make the app look inconsistent.
  • Hardcoding colors in widgets: Overrides the benefits of the color scheme.
  • Ignoring onColors: Text and icons may become unreadable.
  • Not testing dark mode: Colors may clash or become invisible in dark mode.

Advanced: Custom ColorScheme

You can fully customize all properties of a color scheme for complete control:

ColorScheme customScheme = ColorScheme(
  brightness: Brightness.light,
  primary: Colors.blue,
  onPrimary: Colors.white,
  secondary: Colors.orange,
  onSecondary: Colors.black,
  background: Colors.grey[50]!,
  onBackground: Colors.black87,
  surface: Colors.white,
  onSurface: Colors.black87,
  error: Colors.red,
  onError: Colors.white,
)

This allows designing unique, branded color schemes for your application.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *