Skip to content

feat(button): add recipe and tokens - #31628

Draft
thetaPC wants to merge 2 commits into
FW-6838-testsfrom
FW-6838
Draft

thetaPC wants to merge 2 commits into
FW-6838-testsfrom
FW-6838

Conversation

@thetaPC

@thetaPC thetaPC commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

Issue number: resolves internal


What is the current behavior?

ion-button fragments its styles across per-theme stylesheets (.ios, .md, .ionic, .native).

What is the new behavior?

  • Migrates ion-button to a single token-driven button.scss, with IonButtonRecipe / IonButtonConfig and per-theme tokens
  • Draws focus, hover and activated on the native element from the tokens instead of an ::after overlay. Same colors as next; in md the ripple still shows over the normal background
  • Makes icon-only buttons squares as tall as a text button of the same size
  • Syncs iOS heights and padding to the iOS 17 kit (28/34/50px), and iOS clear buttons now use the same font size as the other fills
  • Moves disabled opacity to each fill. ionic uses 0.4, which matches its design's white wash
  • ionic outline buttons use the darker foreground for their label and the page background for their fill, matching the design
  • ionic toolbar buttons are 40px tall, matching the design's top bar
  • Sizes a slotted spinner per spinner size, so a size set on the spinner still applies
  • Moves parent color and size rules to nested tokens (IonToolbar.IonButtons.IonButton, IonDatetime.IonButton, IonInputPasswordToggle.IonButton)
  • Deprecates the default size in favor of medium

Does this introduce a breaking change?

  • Yes
  • No

The button's CSS variables are replaced by token-driven custom properties. Use the global token for theme-wide styling, or the CSS variable for component-specific overrides.

  1. The ios button's border-radius is 6px at every size, and md defaults to 999px instead of 4px. To revert, set shape="soft" for md. For ios, set IonButton.shape.soft.border.radius or --ion-button-shape-soft-border-radius to 14px (and 6px / 16px on small / large).
  2. Icon-only buttons in the start and end slots of ion-input, ion-select and ion-textarea follow the button's shape instead of always being circles. For ios, set shape="round" to keep them circular.
  3. ios clear buttons use the same font size as the other fills, instead of 17px at the default size. To keep it, set --ion-button-size-medium-font-size to 17px.
  4. The ios button is 28px, 34px and 50px tall at the small, default and large sizes, with less padding. To revert, set IonButton.size.<size>.minHeight or --ion-button-size-<size>-min-height to 2.1em, 3.1em and 3.1em, and the padding to 4px 0.9em, 13px 1em and 17px 1em.
  5. Icon-only buttons are squares as tall as a text button of the same size. In md, they're a few pixels smaller at the small and default sizes and larger at the large size.
  6. Replaced CSS variables. Replace <fill> with solid, outline or clear, and <shape> with crisp, soft, round or rectangular. Buttons with a color read the same tokens under semantic.
Old (9.x) New token (global) New CSS variable (component-specific)
--background IonButton.hue.bold.<fill>.default.background --ion-button-hue-bold-<fill>-default-background
--background-activated IonButton.hue.bold.<fill>.activated.background --ion-button-hue-bold-<fill>-activated-background
--background-focused IonButton.hue.bold.<fill>.focus.background --ion-button-hue-bold-<fill>-focus-background
--background-hover IonButton.hue.bold.<fill>.hover.background --ion-button-hue-bold-<fill>-hover-background
--border-color IonButton.hue.bold.<fill>.default.border.color --ion-button-hue-bold-<fill>-default-border-color
--border-radius IonButton.shape.<shape>.border.radius --ion-button-shape-<shape>-border-radius
--border-style IonButton.hue.bold.<fill>.default.border.style --ion-button-hue-bold-<fill>-default-border-style
--border-width IonButton.hue.bold.<fill>.default.border.width --ion-button-hue-bold-<fill>-default-border-width
--box-shadow IonButton.hue.bold.<fill>.default.box.shadow --ion-button-hue-bold-<fill>-default-box-shadow
--color IonButton.hue.bold.<fill>.default.color --ion-button-hue-bold-<fill>-default-color
--color-activated IonButton.hue.bold.<fill>.activated.color --ion-button-hue-bold-<fill>-activated-color
--color-focused IonButton.hue.bold.<fill>.focus.color --ion-button-hue-bold-<fill>-focus-color
--color-hover IonButton.hue.bold.<fill>.hover.color --ion-button-hue-bold-<fill>-hover-color
--ripple-color IonButton.hue.bold.<fill>.rippleEffect.color --ion-button-hue-bold-<fill>-ripple-effect-color
--transition IonButton.transition --ion-button-transition

--background-activated-opacity, --background-focused-opacity and --background-hover-opacity have no one-to-one replacement: set the state's final color instead. --opacity is removed: set opacity on ion-button.

  1. --padding-* variables are no longer documented, but still work when set on the button itself. Replace <size> with small, medium or large:
Old (9.x) New token (global) New CSS variable (component-specific)
--padding-top IonButton.size.<size>.padding.top --ion-button-size-<size>-padding-top
--padding-end IonButton.size.<size>.padding.end --ion-button-size-<size>-padding-end
--padding-bottom IonButton.size.<size>.padding.bottom --ion-button-size-<size>-padding-bottom
--padding-start IonButton.size.<size>.padding.start --ion-button-size-<size>-padding-start
  1. Renamed classes:
Old (9.x) New
.button-solid / .button-outline / .button-clear .button-fill-solid / .button-fill-outline / .button-fill-clear
.button-small / .button-default / .button-large .button-size-small / .button-size-medium / .button-size-large
.button-soft / .button-round / .button-rectangular .button-shape-soft / .button-shape-round / .button-shape-rectangular
.button-block / .button-full .button-expand-block / .button-expand-full
  1. The default size is deprecated. It logs a warning and renders as medium.
  2. Theme classes: remove any styles that target ion-button.md or ion-button.ios.

Other information

Not matched to the design on purpose

Left for the parents

  • ion-buttons, datetime, input, select, textarea, item and list header still set --padding-* on buttons; each moves it to a nested token in its own migration
  • In ios and md toolbars, ion-buttons caps buttons at 32px tall, so the iOS toolbar icon button is 38×32

Known gaps

  • The a11y spec skips a pressed outline button in md's dark toolbar (TODO(FW-6838)). It fails contrast on next too
  • In ionic's dark snapshots, outline buttons show a white background: the test pages pin --ion-background-color to white. In an app, it follows the page

How to test

Check Before After What to look for
iOS sizes Before After Buttons are shorter, with less space above and below the label
Icon-only Before After Icon-only buttons are squares as tall as the text buttons beside them
ionic outline Before After Labels are a darker blue. Disabled buttons are fainter, the same amount as the other fills
ionic toolbar Before After Toolbar buttons are 40px tall, and icon buttons are 40×40 squares
md states Before After Hover, tab to and press and hold each button. The tint and ripple look the same as before
ionic spinner Before After The spinner in large buttons is bigger (24px)

@thetaPC
thetaPC added this pull request to stack #31629 October 21, 2026 23:00
@vercel

vercel Bot commented Oct 21, 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 Oct 22, 2026 12:20am UTC

Request Review

@github-actions github-actions Bot added package: core @ionic/core package package: angular @ionic/angular package labels Oct 21, 2026

This branch was successfully deployed

1 active deployment
Preview — 7ea4a968 Deployed Oct 22, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

package: angular @ionic/angular package package: core @ionic/core package

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant