Actions

Button

How the Button works with a keyboard and assistive tech, and where it stands against WCAG 2.2 AA.

The Button renders a native <button>, so the browser supplies its role, focus and keyboard behaviour. What is left to the component is keeping that intact through every variant, and meeting WCAG 2.2 AA for contrast, focus and target size.

Keyboard#

KeysAction
Tab
Moves focus to the button. A disabled button is skipped.
Shift+Tab
Moves focus to the previous focusable element.
Enter
Activates the button.
Space
Activates the button when the key is released.

Semantics#

AttributeWhenWhat it does
<button type="button">
Always
Native role and keyboard handling. Inside a form, pass type="submit" deliberately.
aria-label
iconOnly
Set from the label, so the button still has a name when no text shows.
aria-busy="true"
loading
Announced as busy. The button is disabled while loading so it can't be pressed twice.
disabled
disabled or loading
Removed from the tab order and not announced as actionable. If users need to find out why an action is unavailable, keep it enabled and explain on press instead.
aria-hidden
Icons
Icons beside a label are decorative and hidden from assistive tech.

Colour contrast#

Every variant and state has to meet WCAG 2.2 Level AA. Measure the colours as they land on screen: a translucent hover or press wash over whatever sits behind the button.

RequirementRatioApplies toGuidance
Text label
4.5:1
Rest, Hover, Pressed
Label text against the button's fill in every state. The label is 10–18px bold, below the large-text threshold, so 4.5:1 applies at every size. Measure a translucent hover or press over the surface behind it.WCAG 1.4.3 Contrast (Minimum)
Icon-only glyph and spinner
3:1
Icon only, Loading
An icon that stands in for text, and the spinner's arc, need 3:1 against the fill. An icon beside a visible label does not need its own check.WCAG 1.4.11 Non-text Contrast
Visible boundary
3:1 if needed
Secondary
The stroke only needs 3:1 where it is the only thing that shows there's a control. A button with a readable label can rely on the label.WCAG 1.4.11 Non-text Contrast
Focus indicator
3:1
Focus visible
The 3px ring (#1a6ef8) sits 3px off the edge with the page colour between, so it contrasts with the page, not the fill.WCAG 1.4.11 Non-text ContrastWCAG 2.4.13 Focus Appearance
Disabled
Not required
Disabled
Inactive controls are exempt from contrast requirements. The disabled label (#cbcbcb) is deliberately faint.WCAG 1.4.3 Contrast (Minimum)

Measured contrast#

Label against fill, for every intent, variant and state, measured from the component's colour tokens. Secondary and Tertiary are measured on white, Inverse on dark on #2a2a2a. 25 of 54 pairs fall below 4.5:1 and are marked in amber.

IntentVariantRestHoverPressed
Neutral
Primary
14.35:1passes
8.59:1passes
20.14:1passes
Neutral
Secondary
5.17:1passes
4.75:1passes
12.04:1passes
Neutral
Tertiary
14.35:1passes
4.75:1passes
12.04:1passes
Brand
Primary
5.40:1passes
8.53:1passes
13.54:1passes
Brand
Secondary
5.40:1passes
4.81:1passes
3.10:1below the minimum
Brand
Tertiary
5.40:1passes
4.81:1passes
3.10:1below the minimum
Success
Primary
1.94:1below the minimum
3.68:1below the minimum
7.62:1passes
Success
Secondary
3.68:1below the minimum
3.37:1below the minimum
2.64:1below the minimum
Success
Tertiary
3.68:1below the minimum
3.56:1below the minimum
3.16:1below the minimum
Warning
Primary
2.27:1below the minimum
4.00:1below the minimum
7.91:1passes
Warning
Secondary
4.00:1below the minimum
3.55:1below the minimum
2.45:1below the minimum
Warning
Tertiary
4.00:1below the minimum
3.86:1below the minimum
3.22:1below the minimum
Error
Primary
4.49:1below the minimum
7.03:1passes
11.60:1passes
Error
Secondary
4.49:1below the minimum
4.17:1below the minimum
3.01:1below the minimum
Error
Tertiary
4.49:1below the minimum
4.17:1below the minimum
3.01:1below the minimum
Inverse on dark
Primary
14.35:1passes
12.82:1passes
8.85:1passes
Inverse on dark
Secondary
14.35:1passes
9.74:1passes
6.58:1passes
Inverse on dark
Tertiary
14.35:1passes
9.74:1passes
6.58:1passes

Target size#

WCAG 2.5.8 (AA) asks for targets of at least 24 × 24px, or enough space around a smaller one. 44px (2.5.5, AAA) is the comfortable size for touch. Width grows with the label, so height is the measure that matters; icon-only buttons are square.

SizePrimary, SecondaryTertiary24px (AA)44px (AAA)
XXS
24px
22px
Needs spacing
—
XS
32px
28px
Meets
—
S
40px
36px
Meets
—
M
48px
44px
Meets
Meets
L
60px
52px
Meets
Meets
XL
72px
60px
Meets
Meets

Motion#

  • Colour changes between states take 150ms and are turned off under prefers-reduced-motion.
  • The loading spinner only spins when motion is allowed. Under reduced motion it stays still, and aria-busy still says the button is working.

Checklist#

  • Every button has a name: visible text, or an aria-label on an icon-only button.
  • The label says what happens (Save changes, Delete project), not OK or Click here.
  • Use a link, not a button, when the only effect is going to another page.
  • Pairs the table marks below 4.5:1 — Success and Warning in most states, Error at rest, and the pressed washes of Secondary and Tertiary — need a darker shade before the code library ships.
  • A Tertiary XXS button is 22px tall, under the 24px minimum, so it passes only on spacing: keep 24px between the centres of neighbouring targets.