Skip to content

[material_ui] Add global Material style variant#11931

Open
QuncCccccc wants to merge 6 commits into
flutter:m3e_migrationfrom
QuncCccccc:add_global_variant
Open

[material_ui] Add global Material style variant#11931
QuncCccccc wants to merge 6 commits into
flutter:m3e_migrationfrom
QuncCccccc:add_global_variant

Conversation

@QuncCccccc

@QuncCccccc QuncCccccc commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

This PR is to add a global StyleVariant setting to ThemeData so Material components can choose between Material 3 and Material 3 Expressive defaults from ThemeData.

enum StyleVariant {
  material3,
  material3Expressive,
}

ThemeData.variant currently defaults to StyleVariant.material3, preserving existing behavior unless apps explicitly opt in to StyleVariant.material3Expressive.

Why default to Material 3?

To align with Cupertino's proposal, which does not provide a default value for StyleVariant, I also considered making ThemeData.variant nullable and adding a runtime check similar to debugCheckHasMaterial. Migrated components could then require an explicit style variant ancestor, and users could receive warnings when a style variant is deprecated.

However, that approach seems too strict for the start of the migration:

  • Many tests pump widgets directly without wrapping them in a Theme or MaterialApp.
  • Users can also use individual Material widgets directly in small tests or examples.
  • Once a component starts reading ThemeData.variant by using something like debugCheckHasStyleVariant, requiring an explicit variant would make those cases fail even though the default behavior should still be Material 3. That breaking change would mainly serve the deprecation flow rather than improve user behavior today.
  • @dkwingsmt mentioned that Cupertino may require annual deprecations, so clear deprecation warnings are especially important there. Material does not have the same requirement, so I think it is reasonable to use other communication or migration tools if we later decide to change the default value or deprecate an old variant.

Therefore, this PR gives the global variant a default value of StyleVariant.material3. This keeps the migration non-breaking while still giving components a single global source of truth for opting into Material 3 Expressive. If the default ever changes later, that can happen as a separate, intentional migration step. In other words, this defers the breaking change until the point where we intentionally change the default variant.

API scope

This PR only adds the variant api to ThemeData. It does not add a MaterialApp.variant shortcut because apps can already configure the value with:

  MaterialApp(
    theme: ThemeData(
      variant: StyleVariant.material3Expressive,
    ),
  )

Also wanted to mention, StyleVariant is intended to select between Material 3 variants. Components that still support useMaterial3: false should continue to use their Material 2 defaults when useMaterial3 is false, regardless of ThemeData.variant. But we should gradually deprecate useMaterial3 since M2 is too out of date.

Open questions

  • Should this PR update CHANGELOG.md and bump pubspec.yaml?

    This is a new public API. However, since this branch targets m3e_migration rather than main, I left those files unchanged for now and would like reviewer guidance on whether migration-branch PRs should update package release metadata immediately or defer that until changes are moved to main.

Pre-Review Checklist

  • I read the [Contributor Guide] and followed the process outlined there for submitting PRs.
  • I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools.
  • I read the [Tree Hygiene] page, which explains my responsibilities.
  • I read and followed the [relevant style guides] and ran [the auto-formatter].
  • I signed the [CLA].
  • The title of the PR starts with the name of the package surrounded by square brackets, e.g. [shared_preferences]
  • I [linked to at least one issue that this PR fixes] in the description above.
  • I followed [the version and CHANGELOG instructions], using [semantic versioning] and the [repository CHANGELOG style], or I have commented below to indicate which documented exception this PR falls under[^1].
  • I updated/added any relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or I have commented below to indicate which [test exemption] this PR falls under[^1].
  • All existing and new tests are passing.

@flutter-dashboard flutter-dashboard Bot added the CICD Run CI/CD label Jun 17, 2026
@github-actions github-actions Bot added triage-framework Should be looked at in framework triage p: material_ui labels Jun 17, 2026
@QuncCccccc QuncCccccc marked this pull request as ready for review June 17, 2026 22:30

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new StyleVariant enum to define Material Design style variants, specifically material3 and material3Expressive. It integrates this variant into ThemeData, updating its constructors, copyWith, lerp, equality checks, and diagnostics. Corresponding unit tests have been added to verify the behavior of the new property. There are no review comments, and no additional feedback is provided.

InteractiveInkFeatureFactory? splashFactory,
bool? useMaterial3,
bool? useSystemColors,
StyleVariant? variant,

@QuncCccccc QuncCccccc Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also open to other name suggestions! I just stole Tong's proposal for the name :)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No better ideas from me, looks good.

@dkwingsmt

dkwingsmt commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

The PR that adds the new variant should also add assertions to all widgets and the global theme at the same time. Otherwise developers would be misled and use the unsupported style by mistake.

@github-actions github-actions Bot removed the CICD Run CI/CD label Jun 18, 2026
@QuncCccccc

QuncCccccc commented Jun 18, 2026

Copy link
Copy Markdown
Contributor Author

I added asserts to both the component theme data and the component build methods.

  • For existing M3 components that have an M3E update, I added the assert.
  • For existing M3 components that do not have an M3E update (or will be replaced by potential new widgets), I skipped them.

By default, each component's MD style will fall back to ThemeData.variant, which is .material3. Introducing this component-theme-level flag allows users to opt in or out of M3E on a component-by-component basis. Once a component gains full M3E support, we will remove the assert from both the build method and the component theme data. Once we finished all M3E update, we will remove the assert from ThemeData.

Question:

If one component doesn't have m3e update now but later it does, do we add the assertion and will it become a breaking change? For example, if previously a component doesn't have m3e update, this will pass:

ThemeData(variant: .material3expressive, xxxWidgetTheme: xxxWidgetThemeData());

But if later it has m3e update and if we add the assertion, this will fail because m3e is not supported. In this case, do we just ignore adding an assertion or use a dart fix to fix the breaking change?

Comment thread packages/material_ui/lib/src/app_bar.dart Outdated
@flutter-dashboard flutter-dashboard Bot added the CICD Run CI/CD label Jun 22, 2026
@Piinks Piinks changed the title Add global Material style variant [material_ui] Add global Material style variant Jun 23, 2026
@QuncCccccc QuncCccccc force-pushed the add_global_variant branch from 166afcd to 539218b Compare June 23, 2026 20:59
@github-actions github-actions Bot removed the CICD Run CI/CD label Jun 23, 2026
@flutter-dashboard flutter-dashboard Bot added the CICD Run CI/CD label Jun 23, 2026

@justinmc justinmc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick review here. I need to finish reading you and @dkwingsmt's latest doc, but currently my main question is about our deprecation strategy. To me it looks like the simplest thing would be: don't deprecate anything until it's possible to migrate completely off of M3, and at that point deprecate the enum value StyleVariant.material3 and change the default value to material3Expressive in a major version bump. Is that what you had in mind? Or just tell me to go read the doc if it's all in there :)

Also, it would be insightful to see a draft PR built on top of this PR that adds M3E support to one of the easier widgets here. No need to spend a bunch of time putting that together, but if you have already tried it or thought it through, I'd be interested to see what it looks like exactly.

InteractiveInkFeatureFactory? splashFactory,
bool? useMaterial3,
bool? useSystemColors,
StyleVariant? variant,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No better ideas from me, looks good.

cupertinoOverrideTheme = cupertinoOverrideTheme?.noDefault();
extensions ??= <ThemeExtension<dynamic>>[];
adaptations ??= <Adaptation<Object>>[];
variant ??= StyleVariant.material3;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this interact with other things that indicate material version, like useMaterial3, or Typography.material2021?

InteractiveInkFeatureFactory? splashFactory,
bool? useMaterial3,
bool? useSystemColors,
StyleVariant? variant,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this really ever nullable, or this is just to set the default?

Comment on lines +909 to +910
final StyleVariant effectiveVariant = appBarTheme.variant ?? theme.variant;
assert(effectiveVariant != .material3Expressive, kUnsupportedStyleVariantAssertionMessage);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So is the StyleVariant always taken from the theme and never passed into a widget as a parameter?

final ThemeData theme = Theme.of(context);
final IconButtonThemeData iconButtonTheme = IconButtonTheme.of(context);
final AppBarThemeData appBarTheme = AppBarTheme.of(context);
final StyleVariant effectiveVariant = appBarTheme.variant ?? theme.variant;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a naive question because I'm forgetting the details of Material's theming, but would it be possible for the AppBarTheme to receive its default value of variant from Theme, so that you don't have to do this xTheme.variant ?? theme.variant everywhere? It strikes me as easy to forget.

If that's not possible or not how this kind of thing is typically done in theming, then never mind.

titleTextStyle: TextStyle.lerp(a.titleTextStyle, b.titleTextStyle, t),
systemOverlayStyle: t < 0.5 ? a.systemOverlayStyle : b.systemOverlayStyle,
actionsPadding: EdgeInsetsGeometry.lerp(a.actionsPadding, b.actionsPadding, t),
variant: t < 0.5 ? a.variant : b.variant,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Would it be useful to abstract this into a StyleVariant.lerp? I see this is done in a handful of places.

@QuncCccccc QuncCccccc requested a review from chunhtai June 30, 2026 22:39
@chunhtai

chunhtai commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Should this PR update CHANGELOG.md and bump pubspec.yaml?

This change definitely need add changelog and bump minor version when it land into main. but it depends on the release strategy material_ui use at that moment, we can revisit this when we want to merge the feature branch back to main

@QuncCccccc

Copy link
Copy Markdown
Contributor Author

don't deprecate anything until it's possible to migrate completely off of M3, and at that point deprecate the enum value StyleVariant.material3 and change the default value to material3Expressive in a major version bump. Is that what you had in mind?

Yes. Most of your understanding is correct! But just wanted to be more clear, since we now consider M3e as an expansion pack for M3, we allow M3 and M3e (and potential M3xxx) to exist at the same time. We don't deprecate M3 until the MD website has clearly mentioned M3 is outdated/M4 is introduced. Until then, we don't need to worry about the deprecation. As for the default value changing, still because we allow M3, M3e and potential expansion packs exist at the same time, we should treat M3e as an opt-in flag, so that we don't need to explicitly change the default value; we will keep m3 as the default value.

If later we have M4, we introduce a new library because it would be considered as a "major change". When M4 is fully supported, we should start to deprecate and remove M3 + M3E + M3X folder all at once. And because M3/M3e and M4 are in 2 folders and no more tangling will happen, removing M3 and its variants should be easy!

As the heavy tangling between M2 and M3, we should gradually deprecate M2 and useMaterial3 flag but it is not the top priority now.

it would be insightful to see a draft PR built on top of this PR that adds M3E support to one of the easier widgets here.

Sure! I'll make IconButton migration ready ASAP. I have an outdated version for it, so need some time to fix:)!

@QuncCccccc QuncCccccc force-pushed the add_global_variant branch from 8bfc3f1 to 3084bc4 Compare July 1, 2026 23:34
@github-actions github-actions Bot removed the CICD Run CI/CD label Jul 1, 2026

@chunhtai chunhtai left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adding the global variant on theme data LGTM, but I am not too sure about start adding the widget specific one without actual implementation. Shouldn't we add individual widget theme variant when we actually implement the m3e design for the widget? it doesn't add value as of now because developer can't set anything meaningful besides lock us in on the API for those widgets

'The color and backgroundColor parameters mean the same thing. Only specify one.',
);
),
assert(variant != .material3Expressive, kUnsupportedStyleVariantAssertionMessage);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should refactor this into a share debug method in debug.dart. if we add more enum variant or update the string, some of these assert may produce broken messaging if we forgot to update them


if (_addPadding) {
final bool useMaterial3 = Theme.of(context).useMaterial3;
final bool useMaterial3 = theme.useMaterial3;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can the useMaterial3 == false at this point? feels weird we have one flag for flipping material 3 and material 2, and another flag to flip between material3 and m3e. is there something we can do to unify them?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

p: material_ui triage-framework Should be looked at in framework triage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants