Skip to content

[material_ui] Inherit IconButtonThemeData.variant from enclosing themes - #13263

Merged
auto-submit[bot] merged 7 commits into
flutter:mainfrom
QuncCccccc:icon_button_variant_inheritance
Nov 4, 2026
Merged

auto-submit[bot] merged 7 commits into
flutter:mainfrom
QuncCccccc:icon_button_variant_inheritance

Conversation

@QuncCccccc

@QuncCccccc QuncCccccc commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

IconButton resolves its Material style variant from the closest IconButtonTheme only. An IconButtonTheme that doesn't set variant therefore resets its subtree to Material 3, even when an ancestor opted into Material 3 Expressive. ListTile, InputDecorator, and AppBar hit this too, because they wrap their children in an internal IconButtonTheme without a variant.

This PR makes a null IconButtonThemeData.variant inherit the variant of the enclosing icon button themes:

  • Adds IconButtonTheme.variantOf, which returns the first non-null variant of the enclosing IconButtonThemes, and then falls back to ThemeData.iconButtonTheme.variant, and then to StyleVariant.material3. IconButton now uses it instead of IconButtonTheme.of(context).variant.
  • Keeps the current behavior of style: the closest theme replaces its ancestors. An explicit variant: StyleVariant.material3 still opts a subtree out.
  • Documents that an enclosing IconButtonTheme takes precedence over ThemeData.iconButtonTheme, and that IconButtonTheme.of only returns the variant specified by the closest theme.

Fixes flutter/flutter#194414

TODO:

  • Follow-up PR:
    • AppBar, ListTile, and InputDecorator set variant: IconButtonTheme.variantOf(context) on the themes they create. This fixes the common cases above.
    • I'll also add some notes to the variant docs: dialogs, bottom sheets, and menus only get the closest theme, so set the variant in ThemeData or on that theme.
  • Update the M3E migration checklist (the migration skill): a theme that a widget creates internally should always set variant, either to variantOf(context) or to StyleVariant.material3.

Pre-Review Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the gemini-code-assist bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.

Footnotes

  1. Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling. ↩ ↩2

@flutter-dashboard flutter-dashboard Bot added the CICD Run CI/CD label Oct 14, 2026
@github-actions github-actions Bot added p: material_ui triage-design Should be looked at in design triage labels Oct 14, 2026
@QuncCccccc
QuncCccccc requested a review from dkwingsmt October 14, 2026 21:36

@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 IconButtonTheme.variantOf, a static method that retrieves the closest non-null IconButtonThemeData.variant from the ancestor tree, falling back to ThemeData.iconButtonTheme.variant. IconButton is updated to use this new method for resolving its style variant. The changes also include updated documentation, a pending changelog entry, and comprehensive unit tests verifying the inheritance, precedence, and rebuilding behavior of the new method. There are no review comments, and I have no feedback to provide.

A null IconButtonThemeData.variant now inherits the variant of the enclosing IconButtonThemes, and then ThemeData.iconButtonTheme, instead of resetting the subtree to Material 3. IconButtonTheme.variantOf returns the variant that applies at a given location, and findClosestStyleVariant lets other components resolve their variant the same way.
@QuncCccccc
QuncCccccc force-pushed the icon_button_variant_inheritance branch from 876a3c0 to 87057de Compare November 2, 2026 20:22

@dkwingsmt dkwingsmt 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.

LGTM.

I'd like to point out that this method misses an edge case as described below. However, this edge case is unlikely to come up in typical cases and addressing it properly would require much broader discussion, so this PR is already a valuable improvement worth landing as-is. I will open an issue or RFC to discuss it.


The edge case is about InheritedTheme.capture, which used in showDialog, showModalBottomSheet, PopupMenuButton, etc. The capture only records the closest InheritedTheme of each runtimeType, if a route has an outer IconButtonTheme that sets variant and an inner IconButtonTheme with variant: null, capturing themes from inside the inner theme (e.g. via showDialog) will only capture the inner one and lose the outer variant in the new route.

  testWidgets('InheritedTheme.captureAll preserves the inherited variant', (
    WidgetTester tester,
  ) async {
    StyleVariant? capturedVariant;
    await tester.pumpWidget(
      MaterialApp(
        home: IconButtonTheme(
          data: const IconButtonThemeData(variant: StyleVariant.material3Expressive),
          child: IconButtonTheme(
            data: const IconButtonThemeData(
              style: ButtonStyle(foregroundColor: WidgetStatePropertyAll<Color>(Color(0xffff0000))),
            ),
            child: Builder(
              builder: (BuildContext context) {
                return ElevatedButton(
                  onPressed: () {
                    Navigator.of(context).push(
                      MaterialPageRoute<void>(
                        builder: (BuildContext routeContext) {
                          return InheritedTheme.captureAll(
                            context,
                            Builder(
                              builder: (BuildContext innerContext) {
                                capturedVariant = IconButtonTheme.variantOf(innerContext);
                                return const SizedBox();
                              },
                            ),
                          );
                        },
                      ),
                    );
                  },
                  child: const Text('Push'),
                );
              },
            ),
          ),
        ),
      ),
    );

    await tester.tap(find.text('Push'));
    await tester.pumpAndSettle();

    expect(capturedVariant, StyleVariant.material3Expressive);
  });

@QuncCccccc

Copy link
Copy Markdown
Contributor Author

The capture only records the closest InheritedTheme of each runtimeType, if a route has an outer IconButtonTheme that sets variant and an inner IconButtonTheme with variant: null, capturing themes from inside the inner theme (e.g. via showDialog) will only capture the inner one and lose the outer variant in the new route.

It makes sense!

This can also happen without a custom nested theme. AppBar, ListTile, and InputDecorator wrap icon buttons in their own IconButtonTheme without a variant. So a dialog or menu opened from one of them loses the variant too, if M3E was turned on with an IconButtonTheme inside the page (for example, around MaterialApp.home). Turning it on through ThemeData isn't affected.

Before we fully solve this problem, I'll do the following (I'll also add these TODOs in PR description):

  1. I'll land this PR as is.
  2. Follow-up PR:
    • AppBar, ListTile, and InputDecorator set variant: IconButtonTheme.variantOf(context) on the themes they create. This fixes the common cases above.
    • I'll also add some notes to the variant docs: dialogs, bottom sheets, and menus only get the closest theme, so set the variant in ThemeData or on that theme.
  3. Update the M3E migration checklist (the migration skill): a theme that a widget creates internally should always set variant, either to variantOf(context) or to StyleVariant.material3.

@QuncCccccc QuncCccccc added the autosubmit Merge PR when tree becomes green via auto submit App label Nov 4, 2026
@auto-submit
auto-submit Bot merged commit 670b30e into flutter:main Nov 4, 2026
13 checks passed
jpohhhh pushed a commit to Telosnex/flutter that referenced this pull request Nov 4, 2026
…r#195977)

flutter/packages@1df8447...670b30e

2026-11-04 36861262+QuncCccccc@users.noreply.github.com [material_ui]
Inherit `IconButtonThemeData.variant` from enclosing themes
(flutter/packages#13263)
2026-11-04 katelovett@google.com [tool] Regenerate `MockGitDir` for
`package:git` 2.5.0 (flutter/packages#13544)
2026-11-03 hkarmoush@gmail.com [material_ui] Fix discrete Slider
selecting the neighboring tick near the track ends
(flutter/packages#12927)
2026-11-03 32538273+ValentinVignal@users.noreply.github.com
[material_ui] Migrate M3 Time Picker template to use new gen_defaults
(flutter/packages#13164)
2026-11-03 269567208+reidbaker-agent@users.noreply.github.com
[camera_android_camerax] Update skills_lint to 0.5.3
(flutter/packages#13530)
2026-11-03 engine-flutter-autoroll@skia.org Roll Flutter from
fcfb4f2 to 9347202 (38 revisions) (flutter/packages#13540)

If this roll has caused a breakage, revert this CL and set the roller
to dry run mode using the controls here:
https://autoroll.skia.org/r/flutter-packages-flutter-autoroll
Please CC flutter-ecosystem@google.com on the revert to ensure that a
human
is aware of the problem.

To file a bug in Flutter:
https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
auto-submit Bot pushed a commit that referenced this pull request Nov 4, 2026
Adds Material 3 Expressive support to `ElevatedButton`. As with `IconButton` (#12832), apps opt in with `ElevatedButtonThemeData(variant: StyleVariant.material3Expressive)`. Without it, `ElevatedButton` keeps the Material 3 style.

With Material 3 Expressive, `ElevatedButton` supports:

* Sizes (`ButtonStyle.sizeVariant`): extra small through extra large. Each size has its own height, padding, icon size, label text style, and gap between the icon and the label.
* Shapes (`ButtonStyle.shapeVariant`): round and square, including the pressed shape and the shape change when a toggle button is selected.
* Toggle buttons: a button with a non-null `isSelected` is selectable, and uses the selected colors and selected semantics when `isSelected` is true.

The defaults are generated from the Material Design tokens by a new `ButtonTemplateM3E` in `gen_defaults`.

New APIs:

* `ElevatedButtonThemeData.variant` and `ElevatedButtonTheme.variantOf`. A theme that doesn't specify `variant` inherits it from the enclosing themes, the same way as `IconButtonThemeData.variant` (#13263).
* `ButtonStyleButton.isSelected` and `ElevatedButton.isSelected`.
* `sizeVariant` and `shapeVariant` parameters for `ElevatedButton.styleFrom`.

<img src="https://gist.githubusercontent.com/QuncCccccc/aba2243afce554b515eec724e62e5650/raw/44b10f43dfb6f42d2c0800928d993cec53df15c2/elevated_button_m3e.png" width="320" alt="Material 3 Expressive ElevatedButton sizes, shapes, toggle buttons, and disabled buttons">

Fixes flutter/flutter#194202
Part of flutter/flutter#184937

## Pre-Review Checklist

**Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.

[^1]: Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling.
dayanruben pushed a commit to dayanruben/flutter that referenced this pull request Nov 5, 2026
~This can only land **after**
flutter/packages#13263

This PR updates the [Material 3 Expressive component migration
checklist](https://github.com/flutter/flutter/blob/master/docs/ecosystem/material_ui/Material-3-Expressive-component-migration-checklist.md)
so that component migrations resolve the style variant with
`<Component>Theme.variantOf`.

With `variantOf`, a nested component theme that doesn't set `variant`
inherits it from its ancestors instead of resetting its subtree to
Material 3. The reference implementation is `IconButtonTheme.variantOf`
in flutter/packages#13263.

* Section 4: adds `variantOf`, its resolution order, and the reference
implementation.
* Section 5: uses `<Component>Theme.variantOf(context)` instead of
`<Component>Theme.of(context).variant`.
* Section 8: adds tests for variant inheritance.
* Section 13: lists `variantOf` in the PR description.

The [Material 3 Expressive migration
skill](https://github.com/flutter/packages/blob/main/.agents/skills/material-3-expressive-migration/SKILL.md)
reads this checklist, so agents pick up the change without a skill
update.

Related to flutter#194414 and
https://github.com/orgs/flutter/projects/250
Related to flutter#194414.

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [AI contribution guidelines] and understand my
responsibilities, or I am not using AI tools.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant in-code documentation (doc comments with
`///`).
- [x] If this PR introduces a new feature or capability, I created and
linked a website documentation issue or PR in [flutter/website] (or
verified none is needed).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.
@dkwingsmt

Copy link
Copy Markdown
Contributor

I've extracted the issue I posted above into flutter/flutter#196177 for better tracking.

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

Labels

autosubmit Merge PR when tree becomes green via auto submit App CICD Run CI/CD p: material_ui triage-design Should be looked at in design triage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[material_ui] Nested IconButtonThemes drop the Material 3 Expressive opt-in (IconButtonThemeData.variant isn't inherited)

2 participants