Skip to main content

Button

A control that commits an action on this screen.

In Dense data

See it live​

Start Storybook to see it live: npm run storybook

When to use it​

In Dense data: 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 Dense data decided and why. Each sample is the live story with that variant set.

primaryvariantUsed in Dense data
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 Dense data

The one commit in a view.

secondaryvariantUsed in Dense data
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 Dense data

Alternatives and cancel next to the primary, and the first step of a delete.

dangervariantUsed in Dense data
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 Dense data

The confirm of a bulk delete. Many records go at once, so the last press must not look like the everyday primary.

ghostvariantUsed in Dense data
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 Dense data

Row actions, as compact icon buttons. Never for cancel.

defaultsizeUsed in Dense data
Start Storybook to see it live

What it is for

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

In Dense data

Actions outside tables: forms, dialogs, page actions.

compactsizeUsed in Dense data
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 Dense data

Table rows and toolbars. Compact still fits a mouse.

iconsizeUsed in Dense data
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 Dense data

Known row and toolbar actions, always with a tooltip.

largesizeNot used in Dense data
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 Dense data

Controls stay small so more records fit on one screen.

hoverstateUsed in Dense data

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 Dense data

Fast feedback that confirms the pointer.

focusstateUsed in Dense data

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 Dense data

Always visible: the focus ring at border-width.focus.

disabledstateUsed in Dense data

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 Dense data

Only with a visible reason next to it.

How Dense data uses it​

An icon with no label
Use Icon button with a tooltip
Not A bare icon
Experts learn icons; the tooltip covers the first uses.
Keyboard focus
Use button/focus-ring at border-width.focus
Not Removing the outline
Keyboard users must always see where they are.
The action that completes the task
Use Button primary, one per view
Not Two primaries side by side
One primary makes the committing action obvious.
An alternative or cancel
Use Button secondary
Not Button ghost
A ghost button has no edge, so next to a filled primary it reads as plain text and cancel gets lost. Secondary keeps a visible border, so people find it at once.
Delete or remove
Use Button secondary, then confirm in a dialog with Button danger
Not Button primary as the first step
Deleting is rare and costly; a confirm step catches mistakes.
An action on one row or item
Use Icon button, ghost, compact
Not Button secondary in every row
Rows repeat dozens of times; a quiet icon keeps the table readable.

Why it works this way​

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

One primary per view; the rest step back.

2So Button
  • ✓uses primary: The one commit in a view.
3If it changed
1
Button decision would reopen

No other answer follows from it.

What is a costly mistake?outcomes.q2
1The answer
Acting on the wrong record

Acting on the wrong row is the costly error.

2So Button
  • ✓uses primary: The one commit in a view.
  • ✓uses secondary: Alternatives and cancel next to the primary, and the first step of a delete.
  • ✓uses danger: The confirm of a bulk delete. Many records go at once, so the last press must not look like the everyday primary.
It also shapes Alert: default Alert: destructive Toast: danger
3If it changed
3
Button decisions would reopen

and 2 answers that follow from it: Status in tables, Errors and empty states.

How do people recall the interface?human.q2
1The answer
They learn it and recognize it

Daily users learn the interface and recognize it.

2So Button
  • ✓uses secondary: Alternatives and cancel next to the primary, and the first step of a delete.
  • ✓uses ghost: Row actions, as compact icon buttons. Never for cancel.
3If it changed
2
Button decisions would reopen

No other answer follows from it.

How often do people repeat actions?tasks.q2
1The answer
Constantly

The same actions repeat many times an hour.

2So Button
  • ✓uses ghost: Row actions, as compact icon buttons. Never for cancel.
It also shapes Input group: inline-end Sidebar: default Alert: default Toast: success Toggle: default
3If it changed
4
Button decisions would reopen

and 11 answers that follow from it: Time on task, All day, Three, and 8 more.

How much is on one screen?content.q2
1The answer
A lot

Experts want many records on one screen.

2So Button
  • ✓uses default: Actions outside tables: forms, dialogs, page actions.
  • ✓uses compact: Table rows and toolbars. Compact still fits a mouse.
  • –does not use large: Controls stay small so more records fit on one screen.
It also shapes Input group: block-start Input group: block-end Field: horizontal Sidebar: lg Toggle: default Toggle: sm Toggle: lg
3If it changed
3
Button decisions would reopen

and 14 answers that follow from it: Quiet, Only the steps the screens need, Small, and 11 more.

What is the smallest target?accessibility.values
1The answer
Compact, for a pointer

Mouse and keyboard; compact targets are enough.

2So Button
  • ✓uses default: Actions outside tables: forms, dialogs, page actions.
  • ✓uses compact: Table rows and toolbars. Compact still fits a mouse.
It also shapes Item: sm Toggle: sm
3If it changed
2
Button decisions would reopen

No other answer follows from it.

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

Icons mark row actions and status.

2So Button
  • ✓uses icon: Known row and toolbar actions, always with a tooltip.
It also shapes Input group: inline-start Empty: icon
3If it changed
1
Button decision would reopen

No other answer follows from it.

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

Fast feedback keeps up with experts.

2So Button
  • ✓uses hover: Fast feedback that confirms the pointer.
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 is the floor for an internal tool used by trained staff.

2So Button
  • ✓uses focus: Always visible: the focus ring at border-width.focus.
  • ✓uses disabled: Only with a visible reason next to it.
3If it changed
2
Button decisions would reopen

and 3 answers that follow from it: A ratio, line heights in proportion, Three: color, icon and word, AA.

Words​

  • Lead with the object, then the state: "Alert 2041 contained."
  • Use numbers, not words for numbers.
  • One verb per button. No "please", no exclamation marks.
  • Use: Investigate, Filter, Assign, Escalated, Resolved. Avoid: Oops, Awesome, Please, Simply.

The labels components use, from the voice tokens:

  • content.action.commit: Save
  • content.action.cancel: Cancel
  • content.action.destroy: Delete
  • content.action.retry: Retry
  • content.action.empty-action: Change filters

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

Dense data 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)Lightviolet.light.600:#4B3FD9Darkviolet.dark.600:#A9A3F6
Background hover
Changes with theme
on hover
component.button.primary.background.hovercolor.action.primary.hovervar(--button-primary-background-hover)Lightviolet.light.700:#3729AADarkviolet.dark.700:#BEBBFB
Text
Changes with theme
component.button.primary.labelcolor.text.on-actionvar(--button-primary-label)Lightslate.light.0:#FFFFFFDarkslate.light.900:#161B23
Secondary
Fill
Changes with theme
component.button.secondary.backgroundcolor.action.secondaryvar(--button-secondary-background)Lightslate.light.0:#FFFFFFDarkslate.dark.50:#161B23
Background hover
Changes with theme
on hover
component.button.secondary.background.hovercolor.action.secondary.hovervar(--button-secondary-background-hover)Lightslate.light.100:#EEF1F5Darkslate.dark.100:#262D38
Text
Changes with theme
component.button.secondary.labelcolor.text.primaryvar(--button-secondary-label)Lightslate.light.900:#161B23Darkslate.dark.900:#F7F8FA
Border
Changes with theme
component.button.secondary.bordercolor.border.strongvar(--button-secondary-border)Lightslate.light.500:#6B7585Darkslate.dark.400:#969FAD
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)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)Lightslate.light.0:#FFFFFFDarkslate.light.900:#161B23
Ghost
Background hover
Changes with theme
on hover
component.button.ghost.background-hovercolor.surface.sunkenvar(--button-ghost-background-hover)Lightslate.light.100:#EEF1F5Darkslate.dark.100:#262D38
Text
Changes with theme
component.button.ghost.labelcolor.text.linkvar(--button-ghost-label)Lightviolet.light.700:#3729AADarkviolet.dark.700:#BEBBFB
Button
Heightcomponent.button.heightsize.control.defaultvar(--button-height)size.32:2rem
Padding, left and rightcomponent.button.padding-xspace.inset.control-xvar(--button-padding-x)space.12:0.75rem
Cornercomponent.button.radiusradius.controlvar(--button-radius)radius.2:0.125rem
Text sizecomponent.button.font-sizetype.label.font-sizevar(--button-font-size)font.size.12:0.75rem
Weightcomponent.button.font-weighttype.label.font-weightvar(--button-font-weight)font.weight.500:500
Focus ring
Changes with theme
on focus
component.button.focus-ringcolor.border.focusvar(--button-focus-ring)Lightviolet.light.600:#4B3FD9Darkviolet.dark.600:#A9A3F6
Icon sizecomponent.button.icon-sizesize.iconvar(--button-icon-size)size.16:1rem
Gapcomponent.button.gapspace.gap.defaultvar(--button-gap)space.8:0.5rem
Font familycomponent.button.font-familytype.label.font-familyvar(--button-font-family)font.family.ibm-plex-sans:"IBM Plex Sans", 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)Lightslate.light.50:#F7F8FADarkslate.dark.0:#0D1117
Disabled
Fill
Changes with theme
when disabled
component.button.disabled.backgroundcolor.disabled.surfacevar(--button-disabled-background)Lightslate.light.100:#EEF1F5Darkslate.dark.100:#262D38
Text
Changes with theme
when disabled
component.button.disabled.labelcolor.disabled.textvar(--button-disabled-label)Lightslate.light.500:#6B7585Darkslate.dark.500:#C4CBD6
Border
Changes with theme
when disabled
component.button.disabled.bordercolor.disabled.bordervar(--button-disabled-border)Lightslate.light.200:#DDE2EADarkslate.dark.200:#3D4655
Compact
Heightcomponent.button.compact.heightsize.control.compactvar(--button-compact-height)size.24:1.5rem
Padding, left and rightcomponent.button.compact.padding-xspace.inset.cell-xvar(--button-compact-padding-x)space.12:0.75rem
Large
Heightcomponent.button.large.heightsize.control.largevar(--button-large-height)size.32:2rem
Shared
Transition durationcomponent.transition.durationmotion.duration.feedbackvar(--transition-duration)duration.fast:120ms
Transition easingcomponent.transition.easingmotion.easing.standardvar(--transition-easing)easing.standard:cubic-bezier(0.2,0,0,1)