Skip to main content

Button

A control that commits an action on this screen.

In Accessible first

See it live​

Start Storybook to see it live: npm run storybook

When to use it​

In Accessible first: Every action, with a large target.

  • Use it when: The press does something here: save, submit, delete, open a dialog, go to the next step.
  • Not when: The press only takes people to another page, or flips a setting that should persist.

Instead:

  • Toggle: An on or off setting that should persist as state.
  • Link: Navigation to another page or section.

Variants​

What each variant is for in any design system, then what Accessible first decided and why. Each sample is the live story with that variant set.

primaryvariantUsed in Accessible first
Start Storybook to see it live

What it is for

The one action this view is for.

✓One commit: Submit, Save, Continue, Start, Next.
✕A second equally loud action on the same view.

In Accessible first

The one next step on a page.

secondaryvariantUsed in Accessible first
Start Storybook to see it live

What it is for

A real action, quieter than the commit.

✓Cancel, Back or Reset next to a primary, or an action with no commit beside it.
✕It is the only action; then it is primary.

In Accessible first

Every other action, always with a word: alternatives, row actions, and the first step of a delete.

dangervariantUsed in Accessible first
Start Storybook to see it live

What it is for

A destructive commit, after a confirm.

✓The confirm step of a delete or remove, with the verb: Delete 40 alerts.
✕The first step of a delete; that is secondary. Anything that does not destroy data.

In Accessible first

The confirm of a delete, always with the verb and an icon, so color is never the only cue.

ghostvariantNot used in Accessible first
Start Storybook to see it live

What it is for

The lowest emphasis; it never competes with the commit.

✓A third option, a card or row action, a toast action.
✕The page's main task; that is primary.

In Accessible first

Every action looks like a button people can find.

defaultsizeUsed in Accessible first
Start Storybook to see it live

What it is for

The normal size, for ordinary actions in forms, dialogs and page chrome.

In Accessible first

Every button. Large targets help people with motor needs.

compactsizeNot used in Accessible first
Start Storybook to see it live

What it is for

Denser, for toolbars and table rows that sit beside content.

✕The page's main commit, or a control too small to hit.

In Accessible first

Large targets help people with motor needs; nothing smaller.

iconsizeNot used in Accessible first
Start Storybook to see it live

What it is for

Icon only: the name is not on screen.

✓A repeated, known control in chrome: close, settings, more.
✕A one-off or destructive action with no accessible name.

In Accessible first

Icons alone are guesswork: always add a word.

largesizeNot used in Accessible first
Start Storybook to see it live

What it is for

The largest size: the one action that must stand out most.

✓The single main call to action of a section or page.
✕Ordinary actions, or more than one per section.

In Accessible first

The default size is already large for motor needs; a bigger one adds nothing.

hoverstateUsed in Accessible first

What it is for

The pointer is over the control: an extra cue, never the only cue.

✓A mouse or trackpad can reach the control.
✕Hover is the only way to tell it is a button; that fails on touch.

In Accessible first

Moderate feedback that is easy to follow.

focusstateUsed in Accessible first

What it is for

Keyboard or assistive tech is on this control. It must stay visible.

✓Tab, Enter, Space, or focus restored after a dialog.
✕The focus outline removed with no replacement.

In Accessible first

Seen by everyone: the focus ring with an offset.

disabledstateNot used in Accessible first

What it is for

The action is not available. It must stay perceivable and say why.

✓A control that must not run now, with a visible reason.
✕A form's submit that could explain the problem on press instead.

In Accessible first

Keep it enabled and explain on press: nobody is left out, and nobody remembers the interface from last time.

How Accessible first uses it​

An icon with no label
Use Not used: always add a word
Not An icon button
Icons alone are guesswork.
Keyboard focus
Use button/focus-ring at border-width.focus, with an offset
Not A thin or faint outline
Focus must be seen by everyone.
The action that completes the task
Use Button primary, one per page
Not Two primaries
One clear next step. Two primaries make people stop and compare, which adds effort.
An alternative or cancel
Use Button secondary
Not A text link
Every action looks like a button people can find.
Delete or remove
Use Button secondary, then a confirm page with Button danger
Not Button primary
A confirm step prevents a costly error.
An action on one row or item
Use Button secondary with a word
Not An icon on its own
A word says what will happen.

Why it works this way​

Button looks the way it does because of 7 answers about this product. Each card is one answer, in three steps: the answer, what it decided for Button, and what would move if it changed.

How many emphasis levels do actions need?color.count
1The answer
Two

Primary and secondary only; fewer choices, fewer errors.

2So Button
  • ✓uses primary: The one next step on a page.
3If it changed
1
Button decision would reopen

No other answer follows from it.

How do people recall the interface?human.q2
1The answer
They see it fresh each time

Nobody remembers the interface from last time.

2So Button
  • ✓uses secondary: Every other action, always with a word: alternatives, row actions, and the first step of a delete.
  • –does not use ghost: Every action looks like a button people can find.
  • –does not use disabled: Keep it enabled and explain on press: nobody is left out, and nobody remembers the interface from last time.
It also shapes Field: responsive Toggle: default
3If it changed
4
Button decisions would reopen

and 1 answer that follow from it: Actions and status.

What is a costly mistake?outcomes.q2
1The answer
A task that fails

A failed form can cost someone a benefit.

2So Button
  • ✓uses secondary: Every other action, always with a word: alternatives, row actions, and the first step of a delete.
  • ✓uses danger: The confirm of a delete, always with the verb and an icon, so color is never the only cue.
It also shapes Alert: destructive Badge: warning Badge: danger
3If it changed
2
Button decisions would reopen

No other answer follows from it.

What accessibility level do we commit to?people.q2
1The answer
AAA

AAA where it applies; nobody is left out.

2So Button
  • ✓uses danger: The confirm of a delete, always with the verb and an icon, so color is never the only cue.
  • ✓uses focus: Seen by everyone: the focus ring with an offset.
  • –does not use disabled: Keep it enabled and explain on press: nobody is left out, and nobody remembers the interface from last time.
It also shapes Field: vertical Field: horizontal Input group: inline-start Input group: inline-end Input group: block-start Input group: block-end
3If it changed
3
Button decisions would reopen

and 15 answers that follow from it: A ratio, line heights in proportion, Neutrals and one brand ramp, Nothing, and 12 more.

What is the smallest target?accessibility.values
1The answer
Large

Large targets for tremor and touch.

2So Button
  • ✓uses default: Every button. Large targets help people with motor needs.
  • –does not use compact: Large targets help people with motor needs; nothing smaller.
  • –does not use large: The default size is already large for motor needs; a bigger one adds nothing.
It also shapes Item: default Item: sm Toggle: default Toggle: sm Toggle: lg
3If it changed
3
Button decisions would reopen

No other answer follows from it.

What do icons do?iconography.roles
1The answer
Actions and status

Icons support words; they never replace them.

2So Button
  • –does not use icon: Icons alone are guesswork: always add a word.
It also shapes Input group: inline-end Toggle: outline
3If it changed
1
Button decision would reopen

No other answer follows from it.

How fast does feedback feel?motion.values
1The answer
Moderate

Moderate speed is easy to follow.

2So Button
  • ✓uses hover: Moderate feedback that is easy to follow.
3If it changed
1
Button decision would reopen

No other answer follows from it.

Words​

  • Use common words. Explain any term you must use.
  • One idea per sentence. Put the action first.
  • Never blame the person.
  • Use: Continue, Change, Start, You. Avoid: Invalid, Utilise, Submit, Error code.

The labels components use, from the voice tokens:

  • content.action.commit: Continue
  • content.action.cancel: Go back
  • content.action.destroy: Delete
  • content.action.retry: Try again
  • content.action.empty-action: Start a new application

See Voice and tone for the whole voice.

Accessibility and keyboard​

KeyboardWhat happensNot
Moving through the page or a dialog.Tab lands on the button in a logical order.The control is skipped because it used HTML disabled.
The button is focused and the person confirms.Enter runs the same action as a click.Enter only works on submit buttons.
The button is focused.Space runs the same action as a click.Space scrolls the page instead.
The button opened a dialog, menu or popover that just closed.After the action closes a surface, focus returns to the button.Focus dropped on the page body.

Test before you ship:

  • The control has a name assistive tech can speak. Any button, especially an icon-only one.
  • The focused control stays visible; color alone is not the cue. Keyboard or assistive-tech focus is on the button.
  • Unavailable stays in the tab order and explains why. The action must not run, but focus must not jump away.
  • A button that stays down exposes its pressed state. Pin, follow, or another sticky on or off on a button.
  • A button that opens something exposes open or closed. The press reveals a menu, panel or more content.
  • The hit area stays usable, including at a small size. Touch, pointer, or a compact toolbar button.

Use it in code​

npx open-ds add button

The component reads only component keys and semantic tokens, so it follows the brand and mode on <html>.

Tokens it reads​

Button reads 32 tokens of its own, each an alias of a semantic token. Each row is one part: its own token, the semantic it aliases, and the value it ends at. Change a semantic and every component that reads it follows; override one of Button's tokens to change Button alone.

Changes with theme

Accessible first decides what changes when you switch theme. Here a switch changes colors only, so the marked rows differ between light and dark; everything else holds steady.

PartIts token and the semantic it aliasesValue
Primary
Fill
Changes with theme
component.button.primary.backgroundcolor.action.primaryvar(--button-primary-background)Lightblue.light.600:#0B4FC0Darkblue.dark.600:#8AB4F8
Background hover
Changes with theme
on hover
component.button.primary.background.hovercolor.action.primary.hovervar(--button-primary-background-hover)Lightblue.light.700:#013895Darkblue.dark.700:#A7C8FC
Text
Changes with theme
component.button.primary.labelcolor.text.on-actionvar(--button-primary-label)Lightgray.light.0:#FFFFFFDarkgray.light.900:#141414
Secondary
Fill
Changes with theme
component.button.secondary.backgroundcolor.action.secondaryvar(--button-secondary-background)Lightgray.light.0:#FFFFFFDarkgray.dark.50:#141414
Background hover
Changes with theme
on hover
component.button.secondary.background.hovercolor.action.secondary.hovervar(--button-secondary-background-hover)Lightgray.light.100:#EDEDEDDarkgray.dark.100:#222222
Text
Changes with theme
component.button.secondary.labelcolor.text.primaryvar(--button-secondary-label)Lightgray.light.900:#141414Darkgray.dark.900:#F7F7F7
Border
Changes with theme
component.button.secondary.bordercolor.border.strongvar(--button-secondary-border)Lightgray.light.500:#595959Darkgray.dark.500:#949494
Border widthcomponent.button.secondary.border-widthborder-width.defaultvar(--button-secondary-border-width)border-width.2:2px
Danger
Fill
Changes with theme
component.button.danger.backgroundcolor.action.dangervar(--button-danger-background)Lightred.light.600:#C42B2BDarkred.dark.600:#F28B8B
Background hover
Changes with theme
on hover
component.button.danger.background.hovercolor.action.danger.hovervar(--button-danger-background-hover)Lightred.light.700:#981519Darkred.dark.700:#FAA9A8
Text
Changes with theme
component.button.danger.labelcolor.text.on-dangervar(--button-danger-label)Lightgray.light.0:#FFFFFFDarkgray.light.900:#141414
Ghost
Background hover
Changes with theme
on hover
component.button.ghost.background-hovercolor.surface.sunkenvar(--button-ghost-background-hover)Lightgray.light.100:#EDEDEDDarkgray.dark.100:#222222
Text
Changes with theme
component.button.ghost.labelcolor.text.linkvar(--button-ghost-label)Lightblue.light.700:#013895Darkblue.dark.700:#A7C8FC
Button
Heightcomponent.button.heightsize.control.defaultvar(--button-height)size.52:3.25rem
Padding, left and rightcomponent.button.padding-xspace.inset.control-xvar(--button-padding-x)space.24:1.5rem
Cornercomponent.button.radiusradius.controlvar(--button-radius)radius.6:0.375rem
Text sizecomponent.button.font-sizetype.label.font-sizevar(--button-font-size)font.size.18:1.125rem
Weightcomponent.button.font-weighttype.label.font-weightvar(--button-font-weight)font.weight.700:700
Focus ring
Changes with theme
on focus
component.button.focus-ringcolor.border.focusvar(--button-focus-ring)Lightblue.light.600:#0B4FC0Darkblue.dark.600:#8AB4F8
Icon sizecomponent.button.icon-sizesize.iconvar(--button-icon-size)size.20:1.25rem
Gapcomponent.button.gapspace.gap.defaultvar(--button-gap)space.16:1rem
Font familycomponent.button.font-familytype.label.font-familyvar(--button-font-family)font.family.atkinson-hyperlegible:"Atkinson Hyperlegible", Verdana, sans-serif
Focus
on focus
component.button.focusborder-width.focusvar(--button-focus)border-width.3:3px
Canvas
Changes with theme
on focus
component.button.canvascolor.surface.canvasvar(--button-canvas)Lightgray.light.50:#F7F7F7Darkgray.dark.0:#0A0A0A
Disabled
Fill
Changes with theme
when disabled
component.button.disabled.backgroundcolor.disabled.surfacevar(--button-disabled-background)Lightgray.light.100:#EDEDEDDarkgray.dark.100:#222222
Text
Changes with theme
when disabled
component.button.disabled.labelcolor.disabled.textvar(--button-disabled-label)Lightgray.light.500:#595959Darkgray.dark.500:#949494
Border
Changes with theme
when disabled
component.button.disabled.bordercolor.disabled.bordervar(--button-disabled-border)Lightgray.light.200:#D6D6D6Darkgray.dark.200:#333333
Compact
Heightcomponent.button.compact.heightsize.control.compactvar(--button-compact-height)size.48:3rem
Padding, left and rightcomponent.button.compact.padding-xspace.inset.cell-xvar(--button-compact-padding-x)space.16:1rem
Large
Heightcomponent.button.large.heightsize.control.largevar(--button-large-height)size.52:3.25rem
Shared
Transition durationcomponent.transition.durationmotion.duration.feedbackvar(--transition-duration)duration.moderate:200ms
Transition easingcomponent.transition.easingmotion.easing.standardvar(--transition-easing)easing.standard:cubic-bezier(0.2,0,0,1)