Skip to content

feat(button): sync the md theme with Material Design 2 - #31636

Open
thetaPC wants to merge 31 commits into
FW-6838-snapshotsfrom
FW-6838-md-sync
Open

thetaPC wants to merge 31 commits into
FW-6838-snapshotsfrom
FW-6838-md-sync

Conversation

@thetaPC

@thetaPC thetaPC commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Issue number: resolves internal


What is the current behavior?

md buttons use values carried over from v9 instead of Material Design 2: 15.4px padding, 0.06em tracking, a 2px outline in the button's color, a 0.5 fade when disabled, and the same elevation on hover and focus as at rest.

What is the new behavior?

md buttons follow Material Design 2, using the values from Material Components Web 14, Google's MD2 implementation:

  • 16px padding and a 64px min width at the default size, and 0.0893em letter spacing
  • 18px icons with an 8px gap to the label, scaled at the small and large sizes
  • A 1px outline in on-surface at 12%, for colored outline buttons too
  • Elevation 4 on hover and focus for solid buttons
  • Disabled buttons use 38% on-surface text and a 12% container with no elevation, instead of fading the whole button. Material shows disabled elements in a neutral color rather than their own, so a disabled button no longer keeps its color (Material Design 2: disabled state)
  • The pressed ripple is 24% on solid buttons and 12% on outline and clear
  • Buttons in ion-buttons have no min width, so an icon without slot="icon-only" doesn't widen them
  • Spinners match the 18px icon size, so a button no longer grows to fit one. MD2 allows a spinner in a button but doesn't size it there, so it follows the icon. Other spinner sizes keep their usual ratio to it

To support this, the recipe gains hover.box.shadow, focus.box.shadow and disabled.{color, border.color, box.shadow}. A theme that doesn't set the disabled colors keeps its default colors, so ios and ionic don't change.

Toolbar and datetime buttons keep their current look: no elevation on hover or focus, and disabled toolbar buttons still fade their own colors.

Snapshots are in #31637.

Does this introduce a breaking change?

  • Yes
  • No

The md button follows Material Design 2 for its spacing, outline, elevation and disabled state. To keep a previous value, set the token for global styles, or the matching --ion-button-* CSS variable for component-specific styles. Replace <fill> with solid, outline or clear, and <size> with small, medium or large.

Property Previous New Token
Padding at the default size 1.1em 1.1429em (16px at the default size) IonButton.size.medium.padding.start and .end
Min width at the default size none 64px IonButton.size.medium.min.width
Letter spacing 0.06em 0.0893em IonButton.letter.spacing
Icon size 1.35em 1.2857em (18px at the default size) IonButton.size.<size>.icon.font.size
Gap between icon and label 0.3em 0.4444em (8px at the default size) IonButton.size.<size>.icon.leading.margin.end and .trailing.margin.start
Spinner in a button the spinner's own size (28px by default) 1.2857em by default (18px at the default size), with the other spinner sizes in their usual ratio to it IonButton.size.<size>.spinner.size.<spinner size>.width and .height
Outline border 2px, the button's color 1px, the text color at 12% IonButton.hue.bold.outline.default.border.width and .color (.semantic.default.border.color with a color)
Solid elevation on hover and focus the resting elevation elevation 4 IonButton.hue.bold.solid.hover.box.shadow and .focus.box.shadow
Disabled the whole button at 0.5 opacity 38% text color, a 12% container and no elevation IonButton.hue.bold.<fill>.disabled.opacity, .color, .background and .box.shadow (.semantic.disabled.color and .background with a color)
Pressed ripple 16% 24% on solid, 12% on outline and clear IonButton.hue.bold.<fill>.rippleEffect.opacity (.semantic.rippleEffect.opacity with a color)

Buttons in toolbars keep their previous colors when disabled. Buttons in ion-buttons also have no min width and no elevation on hover or focus.

Other information

Not matched to MD2 on purpose

Known gaps

  • The a11y spec still skips a pressed outline button in md's dark toolbar (TODO(FW-6417)). The lighter ripple helps, but the text still falls short of WCAG's 4.5:1. The fix belongs to the dark palette

How to test

Check Before After What to look for
Spacing and icons Before After Labels are spaced wider, icons are slightly smaller with more room before the label
Outline and disabled Before After Borders are thin and gray. Disabled buttons are gray instead of faded
Hover and focus Before After Hover over or tab to a solid button: its shadow grows
Toolbars Before After Only the letter spacing changes, and colored outline buttons get the thin gray border

@thetaPC
thetaPC added this pull request to stack #31629 October 22, 2026 22:56
@vercel

vercel Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
ionic-framework Ready Ready Preview Nov 10, 2026 3:05am UTC

Request Review

@thetaPC
thetaPC marked this pull request as ready for review October 29, 2026 16:00
@thetaPC
thetaPC requested a review from a team as a code owner October 29, 2026 16:00
@thetaPC
thetaPC requested a review from brandyscarney October 29, 2026 16:00

@ShaneK ShaneK left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks really good, every md value I checked lines up with the PR description! Just a few things, mainly the readonly datetime buttons going gray and the toolbar icon buttons growing to 64px.

Comment thread core/src/themes/md/default.tokens.ts Resolved
Comment thread BREAKING.md Resolved
Comment thread core/src/themes/md/default.tokens.ts Resolved
Comment thread BREAKING.md Resolved
Comment thread core/src/themes/md/default.tokens.ts Resolved
…md-sync

# Conflicts:
#	core/src/themes/md/default.tokens.ts

@ShaneK ShaneK left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM!

@ShaneK ShaneK left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good to me, great work!

Comment thread BREAKING.md
| Disabled | the whole button at `0.5` opacity | `38%` text color, a `12%` container and no elevation | `IonButton.hue.bold.<fill>.disabled.opacity`, `.color`, `.background` and `.box.shadow` (`.semantic.disabled.color` and `.background` with a `color`) |
| Pressed ripple | `16%` | `24%` on solid, `12%` on outline and clear | `IonButton.hue.bold.<fill>.rippleEffect.opacity` (`.semantic.rippleEffect.opacity` with a `color`) |

Buttons in toolbars keep their previous colors when disabled. Buttons in `ion-buttons` also have no min width and no elevation on hover or focus.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
Buttons in toolbars keep their previous colors when disabled. Buttons in `ion-buttons` also have no min width and no elevation on hover or focus.
Buttons in toolbars keep their previous colors when disabled. Buttons in `ion-buttons` also have no min width, and in a toolbar they have no elevation on hover or focus.

Small follow-up to my earlier comment on this sentence, now that item 7 covers ion-buttons outside a toolbar. The none overrides only exist under IonToolbar.IonButtons, so a solid button in an ion-buttons with no toolbar still goes from elevation 2 to elevation 4 on hover and focus. That matches item 7, but this sentence says it doesn't happen.


IonToolbar: {
// In a toolbar, a disabled button keeps its colors and fades
IonButton: {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could we add IonButton?: IonButtonRecipe; under /** In any toolbar */ in the IonToolbar theme type? No theme put IonButton straight under IonToolbar before, so right now these overrides fall through to [key: string]: any and a typo like disabled.colr compiles without an error. All four default themes still type-check with it added.

This branch was successfully deployed

1 active deployment
Preview — 906fd775 Deployed Nov 10, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

package: core @ionic/core package

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants