Repository navigation
Description
Problem
IconButtonThemeData.variant opts icon buttons into Material 3 Expressive (flutter/packages#12832). IconButton resolves it with:
IconButtonTheme.of(context).variant ?? StyleVariant.material3IconButtonTheme.of only returns the closest IconButtonTheme. If that theme doesn't set variant (for example, it only sets style), its null variant becomes StyleVariant.material3, even when an ancestor opted in. Once any IconButtonTheme widget is above the button, ThemeData.iconButtonTheme isn't consulted either.
Some Material widgets wrap their children in an internal IconButtonTheme that doesn't set a variant, so the opt-in currently has no effect on icon buttons in:
ListTileleading and trailingInputDecoratorprefix and suffix icons (for example, in aTextField)AppBarleading and actions when their icon colors are customized, for example withforegroundColor,iconTheme, oractionsIconTheme
Steps to reproduce
MaterialApp(
theme: ThemeData(
iconButtonTheme: const IconButtonThemeData(variant: StyleVariant.material3Expressive),
),
home: Material(
child: Column(
children: <Widget>[
// Material 3 Expressive: a square (rounded rectangle) button.
IconButton(
onPressed: () {},
style: const ButtonStyle(shapeVariant: ButtonShapeVariant.square),
icon: const Icon(Icons.add),
),
// Material 3: a stadium-shaped button, because ListTile's internal
// IconButtonTheme doesn't set a variant.
ListTile(
title: const Text('Title'),
trailing: IconButton(
onPressed: () {},
style: const ButtonStyle(shapeVariant: ButtonShapeVariant.square),
icon: const Icon(Icons.add),
),
),
],
),
),
)Expected: both buttons use the Material 3 Expressive square shape.
Actual: only the first one does. The same happens when an IconButtonTheme that only sets style is nested inside an IconButtonTheme with variant: StyleVariant.material3Expressive.
Tested with material_ui 1.6.0 and Flutter 3.47.7.
Proposal
Treat a null variant as "inherit" instead of "Material 3", as described in the material_ui and cupertino_ui architecture doc ("Defaults to the closest non-null variant"):
- Add
IconButtonTheme.variantOf(BuildContext). It returns the first non-nullvariantof the enclosingIconButtonThemes, depending on each theme it visits, and then falls back toThemeData.iconButtonTheme.variant.IconButtonwould useIconButtonTheme.variantOf(context) ?? StyleVariant.material3. stylekeeps its current behavior: the closest theme replaces its ancestors.- An explicit
variant: StyleVariant.material3still opts a subtree out. - Every component that adds Material 3 Expressive support uses the same resolution.
Once we have added the fix, we should also mention the following in documentation:
- An
IconButtonThemewidget still takes precedence overThemeData.iconButtonTheme, even when theThemeis closer to the button. This matchesIconButtonTheme.oftoday, so subtrees should opt out with anIconButtonTheme.
Activity
[material_ui] Inherit `IconButtonThemeData.variant` from enclosing th…
Add `variantOf` to the M3E migration checklist (flutter#195099)