Skip to content

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

Closed
flutter/packages
#13263

Description

@QuncCccccc

Problem

IconButtonThemeData.variant opts icon buttons into Material 3 Expressive (flutter/packages#12832). IconButton resolves it with:

IconButtonTheme.of(context).variant ?? StyleVariant.material3

IconButtonTheme.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:

  • ListTile leading and trailing
  • InputDecorator prefix and suffix icons (for example, in a TextField)
  • AppBar leading and actions when their icon colors are customized, for example with foregroundColor, iconTheme, or actionsIconTheme

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-null variant of the enclosing IconButtonThemes, depending on each theme it visits, and then falls back to ThemeData.iconButtonTheme.variant. IconButton would use IconButtonTheme.variantOf(context) ?? StyleVariant.material3.
  • style keeps its current behavior: the closest theme replaces its ancestors.
  • An explicit variant: StyleVariant.material3 still 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 IconButtonTheme widget still takes precedence over ThemeData.iconButtonTheme, even when the Theme is closer to the button. This matches IconButtonTheme.of today, so subtrees should opt out with an IconButtonTheme.

Activity

  1. added
    p: material_uimaterial_ui package in flutter/packages
    packageflutter/packages repository. See also p: labels.
    team-designOwned by Design Languages team
    on Oct 14, 2026
  2. self-assigned this
    on Oct 14, 2026
  3. added
    P2Important issues not at the top of the work list
    triaged-designTriaged by Design Languages team
    on Oct 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

P2Important issues not at the top of the work listp: material_uimaterial_ui package in flutter/packagespackageflutter/packages repository. See also p: 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