Skip to main content

Button

A control that commits an action on this screen.

In Multi-brand

See it live​

Start Storybook to see it live: npm run storybook

When to use it​

In Multi-brand: Every action.

  • 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 Multi-brand decided and why. Each sample is the live story with that variant set.

primaryvariantUsed in Multi-brand
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 Multi-brand

One clear action per view.

secondaryvariantUsed in Multi-brand
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 Multi-brand

The alternative that steps back, and the first step of a delete.

dangervariantUsed in Multi-brand
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 Multi-brand

The confirm of a delete, the same red in every brand: danger never follows the brand.

ghostvariantUsed in Multi-brand
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 Multi-brand

Row actions, with a word.

defaultsizeUsed in Multi-brand
Start Storybook to see it live

What it is for

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

In Multi-brand

Every button, at touch size: customers are often on phones.

compactsizeNot used in Multi-brand
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 Multi-brand

Customers are often on phones: no compact controls.

iconsizeNot used in Multi-brand
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 Multi-brand

Customers use any device and hover hints never show on touch, so every button has a word.

largesizeNot used in Multi-brand
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 Multi-brand

One clear action per view, at the normal size, in every brand.

hoverstateUsed in Multi-brand

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 Multi-brand

Moderate feedback, shared across brands.

focusstateUsed in Multi-brand

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 Multi-brand

Follows the brand, but always shows.

disabledstateUsed in Multi-brand

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 Multi-brand

Only with a visible reason next to it.

How Multi-brand uses it​

An icon with no label
Use Not used: always add a word; a popover explains a fee
Not An icon button with a hover hint
On touch screens hover hints never show; a word says what will happen.
Keyboard focus
Use button/focus-ring (from the brand)
Not Removing the outline
Focus follows the brand but always shows.
The action that completes the task
Use Button primary, one per view
Not Two primaries
One clear action per view. Two primaries split attention.
An alternative or cancel
Use Button secondary
Not A second primary
The alternative steps back, so the one primary stays clear. A second primary would compete with it.
Delete or remove
Use Button secondary, then confirm with Button danger
Not Button primary
Confirm before destroying data.
An action on one row or item
Use Button ghost with a word
Not An icon alone
A word says what will happen.

Why it works this way​

Button looks the way it does because of 8 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.

Which outcome matters most?outcomes.q1
1The answer
Completion without errors

Finished sign-ups and orders.

2So Button
  • ✓uses primary: One clear action per view.
  • –does not use large: One clear action per view, at the normal size, in every brand.
It also shapes Alert: default
3If it changed
2
Button decisions would reopen

No other answer follows from it.

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

Primary, secondary and ghost in every brand.

2So Button
  • ✓uses secondary: The alternative that steps back, and the first step of a delete.
3If it changed
1
Button decision would reopen

No other answer follows from it.

What is a costly mistake?outcomes.q2
1The answer
Looking off-brand

A page that looks like the wrong brand loses trust.

2So Button
  • ✓uses secondary: The alternative that steps back, and the first step of a delete.
  • ✓uses danger: The confirm of a delete, the same red in every brand: danger never follows the brand.
It also shapes Badge: danger Alert: destructive
3If it changed
2
Button decisions would reopen

and 1 answer that follow from it: Also for emphasis and size.

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

Customers see it fresh each time.

2So Button
  • ✓uses ghost: Row actions, with a word.
It also shapes Toggle: outline
3If it changed
1
Button decision would reopen

No other answer follows from it.

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

Customers sign up on phones.

2So Button
  • ✓uses default: Every button, at touch size: customers are often on phones.
  • –does not use compact: Customers are often on phones: no compact controls.
  • –does not use icon: Customers use any device and hover hints never show on touch, so every button has a word.
It also shapes Field: horizontal Item: default Item: sm Toggle: default Toggle: sm
3If it changed
3
Button decisions would reopen

and 1 answer that follow from it: Those and pressed.

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

Icons for actions, status and navigation.

2So Button
  • –does not use icon: Customers use any device and hover hints never show on touch, so every button has a word.
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 across brands.

2So Button
  • ✓uses hover: Moderate feedback, shared across brands.
3If it changed
1
Button decision would reopen

No other answer follows from it.

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

AA in every brand.

2So Button
  • ✓uses focus: Follows the brand, but always shows.
  • ✓uses disabled: Only with a visible reason next to it.
It also shapes Field: vertical Input group: block-start Input group: block-end
3If it changed
2
Button decisions would reopen

and 1 answer that follow from it: AA.

Words​

  • Shared structure: object, state, next step.
  • Brand tone lives in headlines and marketing copy, not in errors.
  • Errors and confirmations read the same in every brand.
  • Use: Get started, Plan, Order. Avoid: Brand-specific words in errors, Jargon.

The labels components use, from the voice tokens:

  • content.action.commit: Save
  • content.action.cancel: Cancel
  • content.action.destroy: Remove
  • content.action.retry: Try again
  • content.action.empty-action: Get started

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

Multi-brand decides what changes when you switch theme. Here a switch changes colors, type and shape, so the marked rows differ between its brands and 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)Aster Lightteal.light.600:#0F766EAster Darkteal.dark.600:#7FCFC6Birch Lightpurple.light.600:#6D28D9Birch Darkpurple.dark.600:#B79DF7Coral Lightorange.light.600:#C2410CCoral Darkorange.dark.600:#F7A67F
Background hover
Changes with theme
on hover
component.button.primary.background.hovercolor.action.primary.hovervar(--button-primary-background-hover)Aster Lightteal.light.700:#015852Aster Darkteal.dark.700:#A1DCD4Birch Lightpurple.light.700:#5212AABirch Darkpurple.dark.700:#C9B7FCCoral Lightorange.light.700:#942D02Coral Darkorange.dark.700:#FCBD9F
Text
Changes with theme
component.button.primary.labelcolor.text.on-actionvar(--button-primary-label)Aster Lightslate-deep.light.0:#FFFFFFAster Darkslate-deep.light.900:#0F172ABirch Lightslate-deep.light.0:#FFFFFFBirch Darkslate-deep.light.900:#0F172ACoral Lightslate-deep.light.0:#FFFFFFCoral Darkslate-deep.light.900:#0F172A
Secondary
Fill
Changes with theme
component.button.secondary.backgroundcolor.action.secondaryvar(--button-secondary-background)Aster Lightslate-deep.light.0:#FFFFFFAster Darkslate-deep.dark.50:#0F172ABirch Lightslate-deep.light.0:#FFFFFFBirch Darkslate-deep.dark.50:#0F172ACoral Lightslate-deep.light.0:#FFFFFFCoral Darkslate-deep.dark.50:#0F172A
Background hover
Changes with theme
on hover
component.button.secondary.background.hovercolor.action.secondary.hovervar(--button-secondary-background-hover)Aster Lightslate-deep.light.100:#F1F5F9Aster Darkslate-deep.dark.100:#1E293BBirch Lightslate-deep.light.100:#F1F5F9Birch Darkslate-deep.dark.100:#1E293BCoral Lightslate-deep.light.100:#F1F5F9Coral Darkslate-deep.dark.100:#1E293B
Text
Changes with theme
component.button.secondary.labelcolor.text.primaryvar(--button-secondary-label)Aster Lightslate-deep.light.900:#0F172AAster Darkslate-deep.dark.900:#F8FAFCBirch Lightslate-deep.light.900:#0F172ABirch Darkslate-deep.dark.900:#F8FAFCCoral Lightslate-deep.light.900:#0F172ACoral Darkslate-deep.dark.900:#F8FAFC
Border
Changes with theme
component.button.secondary.bordercolor.border.strongvar(--button-secondary-border)Aster Lightslate-deep.light.500:#64748BAster Darkslate-deep.dark.400:#96A3B5Birch Lightslate-deep.light.500:#64748BBirch Darkslate-deep.dark.400:#96A3B5Coral Lightslate-deep.light.500:#64748BCoral Darkslate-deep.dark.400:#96A3B5
Border widthcomponent.button.secondary.border-widthborder-width.defaultvar(--button-secondary-border-width)border-width.1:1px
Danger
Fill
Changes with theme
component.button.danger.backgroundcolor.action.dangervar(--button-danger-background)Aster Lightred.light.600:#C42B2BAster Darkred.dark.600:#F28B8BBirch Lightred.light.600:#C42B2BBirch Darkred.dark.600:#F28B8BCoral Lightred.light.600:#C42B2BCoral Darkred.dark.600:#F28B8B
Background hover
Changes with theme
on hover
component.button.danger.background.hovercolor.action.danger.hovervar(--button-danger-background-hover)Aster Lightred.light.700:#981519Aster Darkred.dark.700:#FAA9A8Birch Lightred.light.700:#981519Birch Darkred.dark.700:#FAA9A8Coral Lightred.light.700:#981519Coral Darkred.dark.700:#FAA9A8
Text
Changes with theme
component.button.danger.labelcolor.text.on-dangervar(--button-danger-label)Aster Lightslate-deep.light.0:#FFFFFFAster Darkslate-deep.light.900:#0F172ABirch Lightslate-deep.light.0:#FFFFFFBirch Darkslate-deep.light.900:#0F172ACoral Lightslate-deep.light.0:#FFFFFFCoral Darkslate-deep.light.900:#0F172A
Ghost
Background hover
Changes with theme
on hover
component.button.ghost.background-hovercolor.surface.sunkenvar(--button-ghost-background-hover)Aster Lightslate-deep.light.100:#F1F5F9Aster Darkslate-deep.dark.100:#1E293BBirch Lightslate-deep.light.100:#F1F5F9Birch Darkslate-deep.dark.100:#1E293BCoral Lightslate-deep.light.100:#F1F5F9Coral Darkslate-deep.dark.100:#1E293B
Text
Changes with theme
component.button.ghost.labelcolor.text.linkvar(--button-ghost-label)Aster Lightteal.light.700:#015852Aster Darkteal.dark.700:#A1DCD4Birch Lightpurple.light.700:#5212AABirch Darkpurple.dark.700:#C9B7FCCoral Lightorange.light.700:#942D02Coral Darkorange.dark.700:#FCBD9F
Button
Heightcomponent.button.heightsize.control.defaultvar(--button-height)size.44:2.75rem
Padding, left and rightcomponent.button.padding-xspace.inset.control-xvar(--button-padding-x)space.16:1rem
Corner
Changes with theme
component.button.radiusradius.controlvar(--button-radius)Aster Lightradius.4:0.25remAster Darkradius.4:0.25remBirch Lightradius.12:0.75remBirch Darkradius.12:0.75remCoral Lightradius.999:9999pxCoral Darkradius.999:9999px
Text sizecomponent.button.font-sizetype.label.font-sizevar(--button-font-size)font.size.14:0.875rem
Weightcomponent.button.font-weighttype.label.font-weightvar(--button-font-weight)font.weight.600:600
Focus ring
Changes with theme
on focus
component.button.focus-ringcolor.border.focusvar(--button-focus-ring)Aster Lightteal.light.600:#0F766EAster Darkteal.dark.600:#7FCFC6Birch Lightpurple.light.600:#6D28D9Birch Darkpurple.dark.600:#B79DF7Coral Lightorange.light.600:#C2410CCoral Darkorange.dark.600:#F7A67F
Icon sizecomponent.button.icon-sizesize.iconvar(--button-icon-size)size.16:1rem
Gapcomponent.button.gapspace.gap.defaultvar(--button-gap)space.12:0.75rem
Font family
Changes with theme
component.button.font-familytype.label.font-familyvar(--button-font-family)Aster Lightfont.family.inter:Inter, system-ui, sans-serifAster Darkfont.family.inter:Inter, system-ui, sans-serifBirch Lightfont.family.plus-jakarta-sans:"Plus Jakarta Sans", system-ui, sans-serifBirch Darkfont.family.plus-jakarta-sans:"Plus Jakarta Sans", system-ui, sans-serifCoral Lightfont.family.poppins:Poppins, system-ui, sans-serifCoral Darkfont.family.poppins:Poppins, system-ui, sans-serif
Focus
on focus
component.button.focusborder-width.focusvar(--button-focus)border-width.2:2px
Canvas
Changes with theme
on focus
component.button.canvascolor.surface.canvasvar(--button-canvas)Aster Lightslate-deep.light.50:#F8FAFCAster Darkslate-deep.dark.0:#020617Birch Lightslate-deep.light.50:#F8FAFCBirch Darkslate-deep.dark.0:#020617Coral Lightslate-deep.light.50:#F8FAFCCoral Darkslate-deep.dark.0:#020617
Disabled
Fill
Changes with theme
when disabled
component.button.disabled.backgroundcolor.disabled.surfacevar(--button-disabled-background)Aster Lightslate-deep.light.100:#F1F5F9Aster Darkslate-deep.dark.100:#1E293BBirch Lightslate-deep.light.100:#F1F5F9Birch Darkslate-deep.dark.100:#1E293BCoral Lightslate-deep.light.100:#F1F5F9Coral Darkslate-deep.dark.100:#1E293B
Text
Changes with theme
when disabled
component.button.disabled.labelcolor.disabled.textvar(--button-disabled-label)Aster Lightslate-deep.light.500:#64748BAster Darkslate-deep.dark.500:#CBD5E1Birch Lightslate-deep.light.500:#64748BBirch Darkslate-deep.dark.500:#CBD5E1Coral Lightslate-deep.light.500:#64748BCoral Darkslate-deep.dark.500:#CBD5E1
Border
Changes with theme
when disabled
component.button.disabled.bordercolor.disabled.bordervar(--button-disabled-border)Aster Lightslate-deep.light.200:#E2E8F0Aster Darkslate-deep.dark.200:#334155Birch Lightslate-deep.light.200:#E2E8F0Birch Darkslate-deep.dark.200:#334155Coral Lightslate-deep.light.200:#E2E8F0Coral Darkslate-deep.dark.200:#334155
Compact
Heightcomponent.button.compact.heightsize.control.compactvar(--button-compact-height)size.36:2.25rem
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.44:2.75rem
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)