Skip to content

feat(button): add the subtle hue - #31649

Open
thetaPC wants to merge 36 commits into
FW-6838-icon-onlyfrom
FW-6838-hue
Open

thetaPC wants to merge 36 commits into
FW-6838-icon-onlyfrom
FW-6838-hue

Conversation

@thetaPC

@thetaPC thetaPC commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Issue number: resolves internal


What is the current behavior?

Buttons only have bold colors. Unlike chip and badge, which also have a color prop, there's no hue prop for muted colors.

What is the new behavior?

  • Adds a hue prop to ion-button. It reads the prop first, then IonButton.hue in the theme config, and defaults to bold, like chip
  • Adds hue.subtle to the recipe in every theme, with the same fills, states, semantic colors, ripple and disabled styles as bold
  • Subtle colors come from the shared subtle palette that chip, badge and text already use:
    • solid: the subtle fill with its paired contrast text
    • outline: the same background as bold (transparent, or the page color in ionic), foreground text and a subtle shade border. md keeps its neutral 12% border
    • clear: transparent with foreground text
  • Text on the page or a translucent tint uses foreground, and text on an opaque fill of the color uses contrast, matching ion-text and FW-6422
  • md and ios outline buttons use foreground for their text too, like clear. In ios, clear buttons with a color also keep foreground on hover and focus
  • Buttons inside ion-datetime and ion-input-password-toggle stay bold, since those components only style the bold tokens, so a hue from the config doesn't restyle them
  • Adds a hue test page that shows both hues in every fill, color and state
  • Adds an axe check for subtle buttons in the light and dark palettes, without the colors listed under Known gaps
  • Snapshots are in chore(button): update snapshots for the subtle hue #31656.

Does this introduce a breaking change?

  • Yes
  • No

The text of outline buttons in md and ios uses the color's darker foreground instead of base. In ios, clear buttons with a color also keep foreground on hover and focus. To revert, set IonButton.hue.bold.outline.<state>.color (.semantic.<state>.color with a color) for global styles, or --ion-button-hue-bold-outline-<state>-color for component-specific styles, to the color's base. For ios clear buttons with a color, set IonButton.hue.bold.clear.semantic.<state>.color or --ion-button-hue-bold-clear-semantic-<state>-color for hover and focus to the color's base as well.

Other information

Why these choices

  • The shared subtle palette instead of iOS 17's tinted button: the iOS 17 kit's Bezeled style is the button's color at 15% (#D9E5FC for primary), measured from the kit and from a native UIButton in its tinted configuration. The shared palette (#EBF1FD) matches ios chips and badges, works for every color and follows the palette in dark mode. iOS 17's value would make subtle buttons darker than the subtle chips beside them
  • md and ionic have no subtle design: MD2 has no tonal button, and the ionic design only has fill, outline and ghost. Both follow the shared subtle palette
  • Outline keeps bold's background: a subtle fill behind an outline would make it look like a subtle solid button
  • Outline text uses foreground in md and ios: the text sits on the page, not on a fill of the color, so it follows the same rule as clear. Using base made outline labels lighter than clear labels in the same color

Known gaps

  • Subtle light, warning and success text fails WCAG's 4.5:1 on white, the same as in subtle chips. The colors come from the shared palette, so the fix belongs there (FW-6417). Subtle light also fails in dark mode. The axe check leaves these colors out until FW-6417 fixes them
  • Toolbars only override bold, so a subtle button in a toolbar uses the theme's subtle tokens. The ion-buttons and toolbar migrations can add their own

How to test

Check Before After What to look for
Both hues, ios After Each fill shows bold, then subtle, in every color. Subtle solid has a pale fill with colored text, and subtle outline and clear have darker colored text
Both hues, md After Same as ios, with the ripple on press and elevation on subtle solid
Both hues, ionic After Hovering or pressing subtle outline and clear fills them with the subtle shade
Outline text, md Before After Colored outline labels are darker, matching clear buttons
Outline text, ios Before After Same as md

@thetaPC
thetaPC added this pull request to stack #31629 October 24, 2026 03:07
@vercel

vercel Bot commented Oct 24, 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 1:44am UTC

Request Review

@github-actions github-actions Bot added package: core @ionic/core package package: angular @ionic/angular package package: vue @ionic/vue package labels Oct 24, 2026
@thetaPC
thetaPC marked this pull request as ready for review October 29, 2026 16:03
@thetaPC
thetaPC requested a review from a team as a code owner October 29, 2026 16:03
@thetaPC
thetaPC requested a review from brandyscarney October 29, 2026 16:03

@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, the subtle tokens line up with bold in every theme! Just a couple of things, mainly the datetime and password toggle buttons picking up a subtle hue from the config.

*/
get hueValue(): Hue {
return config.getObjectValue('IonButton.hue', 'bold') as Hue;
const hueConfig = config.getObjectValue('IonButton.hue', 'bold') as Hue;

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.

Setting IonButton.hue to subtle in the theme config also reaches the buttons inside ion-datetime and ion-input-password-toggle, since they don't set hue, and their overrides only cover bold. With that config the md datetime month arrows go from gray rgb(89, 89, 89) to rgb(0, 74, 205), and the ionic password toggle goes from rgb(108, 114, 122) to the same blue. Could those internal buttons pass hue="bold", or would you rather add subtle overrides like the toolbar follow-up?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Went with hue="bold" in ccbad11. Datetime's five buttons and the password toggle only get styled through the bold tokens, so a configured hue shouldn't reach them. With IonButton.hue set to subtle, the md month arrows stay rgb(89, 89, 89) and the ionic password toggle stays rgb(108, 114, 122), while regular buttons still follow the config.

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.

Thanks, that fixes it! Could we add tests for this though? Nothing covers the hue="bold" on those six buttons or the this.hue ?? hueConfig order, so dropping one of the attributes or flipping the ?? would bring the subtle month arrows and toggle back with every test still passing. A button.spec.ts case with IonButton.hue: 'subtle' in the config and hue="bold" on the button would cover the precedence, and datetime and password toggle specs with the same config that check their buttons keep button-hue-bold would cover the attributes.

Comment thread BREAKING.md Resolved

@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, thanks for the fixes, just a couple of small follow-ups on the tests!

*/
get hueValue(): Hue {
return config.getObjectValue('IonButton.hue', 'bold') as Hue;
const hueConfig = config.getObjectValue('IonButton.hue', 'bold') as Hue;

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.

Thanks, that fixes it! Could we add tests for this though? Nothing covers the hue="bold" on those six buttons or the this.hue ?? hueConfig order, so dropping one of the attributes or flipping the ?? would bring the subtle month arrows and toggle back with every test still passing. A button.spec.ts case with IonButton.hue: 'subtle' in the config and hue="bold" on the button would cover the precedence, and datetime and password toggle specs with the same config that check their buttons keep button-hue-bold would cover the attributes.

Comment on lines +51 to +56
.row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 4px;
}

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
.row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 4px;
}
.row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 10px;
margin-block-end: 10px;
}

With a 4px gap the ionic focus rings overlap the neighboring buttons, both side to side and between wrapped rows, since the ring reaches 4px outside the button. That makes the ionic-md hue screenshots hard to read when someone changes the focus ring later. A 10px gap matches the states test and keeps every ring at least 2px clear. The hue snapshots would need regenerating with this.

# Conflicts:
#	BREAKING.md
#	core/src/components/button/button.scss
#	core/src/themes/md/default.tokens.ts

This branch was successfully deployed

1 active deployment
Preview — cefb8d44 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: angular @ionic/angular package package: core @ionic/core package package: vue @ionic/vue package

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants