Skip to content

Proposal: Add themeBuilder to MaterialApp to eliminate theme duplication #185189

@chika3742

Description

@chika3742

Use case

Flutter recommends using ThemeData.from(colorScheme: ...) together with ColorScheme.fromSeed(..., brightness: brightness). With this pattern, the only thing that differs between light and dark themes is Brightness — almost of settings not related to color (typography, shape, component themes, some of ThemeExtensions, etc.) are identical.

However, MaterialApp only accepts fully-specified ThemeData objects for each of theme, darkTheme, highContrastTheme, and highContrastDarkTheme. The only way to avoid duplication is to define a helper function on the user side:

MaterialApp(
  theme:              _buildTheme(Brightness.light, false),
  darkTheme:          _buildTheme(Brightness.dark,  false),
  highContrastTheme:  _buildTheme(Brightness.light, true),
  highContrastDarkTheme: _buildTheme(Brightness.dark, true),
)

ThemeData _buildTheme(Brightness brightness, bool highContrast) =>
    ThemeData.from(
      colorScheme: ColorScheme.fromSeed(
        seedColor: kSeedColor,
        brightness: brightness,
      ),
    ).copyWith(
      cardTheme: ...,
      inputDecorationTheme: ...,
      extensions: [AppColors.fromBrightness(brightness, highContrast)],
    );

The framework offers no first-class support for this pattern, so developers unfamiliar with it naturally write separate ThemeData objects for each property, leading to drift over time.

Proposal

Add a themeBuilder property to MaterialApp with the following signature:

themeBuilder: (Brightness brightness, bool highContrast) => ThemeData

Example usage:

MaterialApp(
  themeBuilder: (brightness, highContrast) => ThemeData.from(
    colorScheme: ColorScheme.fromSeed(
      seedColor: Colors.blue,
      brightness: brightness,
    ),
  ).copyWith(
    cardTheme: const CardThemeData(elevation: 2),
    inputDecorationTheme: const InputDecorationTheme(
      border: OutlineInputBorder(),
    ),
    extensions: [
      AppColors.fromBrightness(
        brightness,
        highContrast,
      ),
    ],
  ),
  themeMode: ThemeMode.system,
)

When themeBuilder is provided, MaterialApp calls it internally with the resolved Brightness and highContrast, replacing the need for theme, darkTheme, highContrastTheme, and highContrastDarkTheme. If dark theme is not needed, the brightness argument can simply be ignored.themeBuilder and those four properties would be mutually exclusive (assertion error if both are set). themeMode continues to work as-is.

Additionally, since there is no need to instantiate a separate ThemeData for each theme variant upfront, there may be a marginal performance improvement.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Issues that are less important to the Flutter projectc: proposalA detailed proposal for a change to Flutterf: material designflutter/packages/flutter/material repository.frameworkflutter/packages/flutter repository. See also f: labels.team-designOwned by Design Languages teamtriaged-designTriaged by Design Languages team

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions