Button

Action button or link with variants, sizes, and a loading state.

Overview

  • Use buttons for actions and anchors for navigation. Disabled and loading states suppress activation.

Aliases

cta, action button

When to use

  • Use Button for primary and secondary actions.
  • Use the link variant for an action with text-link styling.
  • Set loading while an action runs and disabled when an action is unavailable.

When not to use

  • Do not use Button for passive navigation lists or inline text links inside paragraphs.
  • Provide an accessible label for icon-only buttons.

Structural parts

interactive container

root

Carries the main action styling, spacing, and state transitions.

text label

label

Communicates the action in plain language.

supporting icon slot

leading

Adds emphasis or direction before the label.

supporting icon slot

trailing

Adds continuation or disclosure after the label.

loading indicator

spinner

Replaces the icon/leading slot during loading states.

Public interface

Props
NameTypeDefaultDescription
variant'solid' | 'soft' | 'outline' | 'ghost' | 'link' | 'destructive''solid'Chooses the visual action recipe.
size'xs' | 'sm' | 'md' | 'lg''md'Chooses the compiled size recipe.
hrefstring—When provided, renders the button as an anchor element.
as'button' | 'a''button'Explicitly chooses the underlying interactive element.
type'button' | 'submit' | 'reset''button'Native button type when rendered as a button.
disabledbooleanfalseDisables activation and updates semantics accordingly.
loadingbooleanfalseShows the loading state and suppresses activation.
iconOnlybooleanfalseSwitches the component into icon-only mode.
ariaLabelstring—Required accessible label when iconOnly is true.
targetstring—Anchor target when rendering as a link.
relstring—Anchor relationship attributes when rendering as a link.
onClick(event: MouseEvent) => void—Receives native activation for buttons and links; disabled and loading states suppress activation.
themeThemeContract—Sets the theme used to resolve component styles.
Events
NamePayloadDescription
clickMouseEventForwards native activation to legacy on:click handlers.
Slots
NameDescription
defaultPrimary visible label content.
leadingOptional icon or symbol before the label.
trailingOptional icon or symbol after the label.

Variants, sizes, and states

Variants

  • solid: Highest-emphasis action for the main next step.
  • soft: Filled secondary action.
  • outline: Bordered action with a surface-colored fill.
  • ghost: Low-emphasis action for denser surfaces.
  • link: Text-link styling within the button layout.
  • destructive: Destructive action styling.

Sizes

  • xs / sm: Compact sizes for dense UI and supporting actions.
  • md: Default button scale for most product surfaces.
  • lg: Higher-emphasis action size for spacious or touch-focused contexts.

States

  • rest: Default compiled appearance.
  • hover: Color or shadow changes on hover, depending on the variant.
  • focus-visible: Visible keyboard focus treatment using the system focus ring.
  • pressed: Subtle active-state shift without scale distortion.
  • disabled: Visually and semantically non-interactive state.
  • loading: Loading indicator with activation disabled.

Accessibility

Semantics

  • Renders as a native button by default or as an anchor when href or as="a" is supplied.
  • Preserves disabled semantics for buttons and aria-disabled behavior for anchors.

Keyboard

  • Supports native button activation with Enter and Space.
  • Keyboard focus has a visible outline.

Screen readers

  • Label text is exposed directly from the visible content or ariaLabel in icon-only mode.
  • Loading buttons stay in the accessibility tree while suppressing repeated activation.

Caveats

  • Icon-only buttons require ariaLabel so the action remains understandable to assistive technology.

Checklist

  • Verify that the control has an accessible name.
  • Verify that keyboard focus is visible in every state and size.
  • Identify disabled, selected, and error states with text or symbols as well as color.

Tokens and CSS variables

root

--dk-button-bg
#2072e4
--dk-button-fg
#ffffff
--dk-button-border
#2072e4
--dk-button-border-width
1px
--dk-button-radius
clamp(0.75rem, 0.6875rem + 0.278vw, 0.938rem)
--dk-button-min-size
44px
--dk-button-block-size
48px
--dk-button-inline-padding
clamp(1.333rem, 1.2222rem + 0.494vw, 1.667rem)
--dk-button-gap
clamp(0.75rem, 0.6875rem + 0.278vw, 0.938rem)
--dk-button-shadow
0 1px 2px rgba(0, 0, 0, 0.04)
--dk-button-focus-ring-color
#2072e4
--dk-button-focus-ring-width
3px
--dk-button-focus-ring-offset
2px
--dk-button-translate-y
0px
--dk-button-opacity
1
--dk-button-loading-opacity
0.82
--dk-button-cursor
pointer
--dk-button-transition-duration
200ms

label

--dk-button-label-font-size
clamp(1.333rem, 1.2222rem + 0.494vw, 1.667rem)
--dk-button-label-font-weight
600
--dk-button-label-line-height
1.1
--dk-button-label-decoration
none
--dk-button-label-opacity
1

leading

--dk-button-icon-size
18px

trailing

--dk-button-icon-size
18px

icon

--dk-button-icon-size
18px

spinner

--dk-button-spinner-size
18px

Implementation notes

  • The callback and legacy click event receive the original MouseEvent. Call preventDefault() to cancel the default action.

Examples

Starter

Primary action

The default solid medium button for the main next step.

<Button variant="solid" size="md">Continue</Button>

Common patterns

Destructive loading action

Use destructive plus loading when the outcome is risky and asynchronous.

<Button variant="destructive" loading={true}>Delete project</Button>
  • Loading suppresses repeated activation.

Edge cases

Icon-only action

Icon-only mode keeps the target size while shrinking the visual footprint.

<Button variant="ghost" iconOnly={true} ariaLabel="Open settings">⚙</Button>
  • Always provide ariaLabel in icon-only mode.

Mathematical fixtures

Calculated fixtures; browser evidence is not collected. Layout estimates use declared text behavior and maximum token values.

solid-sizes (content=label|size=md|variant=solid)

pass

content=label|size=md|variant=solid

  • Evidence: mathematical
  • Coverage: complete (3 / 3 declared kinds evaluated)
  • Declared: contrast, target, layout
  • Evaluated: contrast, target, layout
  • Contrast: 76.6 Lc
  • Target: 48 px
  • Layout: root · wrap-anywhere 173 px single-line estimate; 68 px minimum width
    180 px: pass 1 line; 48 px estimated height240 px: pass 1 line; 48 px estimated height320 px: pass 1 line; 48 px estimated height

variants-md-label (content=label|size=md|variant=soft)

pass

content=label|size=md|variant=soft

  • Evidence: mathematical
  • Coverage: complete (3 / 3 declared kinds evaluated)
  • Declared: contrast, target, layout
  • Evaluated: contrast, target, layout
  • Contrast: 89.9 Lc
  • Target: 48 px
  • Layout: root · wrap-anywhere 173 px single-line estimate; 68 px minimum width
    180 px: pass 1 line; 48 px estimated height240 px: pass 1 line; 48 px estimated height320 px: pass 1 line; 48 px estimated height

variants-md-label (content=label|size=md|variant=outline)

pass

content=label|size=md|variant=outline

  • Evidence: mathematical
  • Coverage: complete (3 / 3 declared kinds evaluated)
  • Declared: contrast, target, layout
  • Evaluated: contrast, target, layout
  • Contrast: 65.1 Lc
  • Target: 48 px
  • Layout: root · wrap-anywhere 173 px single-line estimate; 68 px minimum width
    180 px: pass 1 line; 48 px estimated height240 px: pass 1 line; 48 px estimated height320 px: pass 1 line; 48 px estimated height

variants-md-label (content=label|size=md|variant=ghost)

pass

content=label|size=md|variant=ghost

  • Evidence: mathematical
  • Coverage: complete (3 / 3 declared kinds evaluated)
  • Declared: contrast, target, layout
  • Evaluated: contrast, target, layout
  • Contrast: 65.1 Lc
  • Target: 48 px
  • Layout: root · wrap-anywhere 173 px single-line estimate; 68 px minimum width
    180 px: pass 1 line; 48 px estimated height240 px: pass 1 line; 48 px estimated height320 px: pass 1 line; 48 px estimated height

variants-md-label (content=label|size=md|variant=link)

pass

content=label|size=md|variant=link

  • Evidence: mathematical
  • Coverage: complete (3 / 3 declared kinds evaluated)
  • Declared: contrast, target, layout
  • Evaluated: contrast, target, layout
  • Contrast: 65.1 Lc
  • Target: 48 px
  • Layout: root · wrap-anywhere 173 px single-line estimate; 68 px minimum width
    180 px: pass 1 line; 48 px estimated height240 px: pass 1 line; 48 px estimated height320 px: pass 1 line; 48 px estimated height

variants-md-label (content=label|size=md|variant=destructive)

pass

content=label|size=md|variant=destructive

  • Evidence: mathematical
  • Coverage: complete (3 / 3 declared kinds evaluated)
  • Declared: contrast, target, layout
  • Evaluated: contrast, target, layout
  • Contrast: 80.5 Lc
  • Target: 48 px
  • Layout: root · wrap-anywhere 173 px single-line estimate; 68 px minimum width
    180 px: pass 1 line; 48 px estimated height240 px: pass 1 line; 48 px estimated height320 px: pass 1 line; 48 px estimated height

solid-sizes (content=label|size=xs|variant=solid)

pass

content=label|size=xs|variant=solid

  • Evidence: mathematical
  • Coverage: complete (3 / 3 declared kinds evaluated)
  • Declared: contrast, target, layout
  • Evaluated: contrast, target, layout
  • Contrast: 76.6 Lc
  • Target: 44 px
  • Layout: root · wrap-anywhere 150 px single-line estimate; 45 px minimum width
    180 px: pass 1 line; 44 px estimated height240 px: pass 1 line; 44 px estimated height320 px: pass 1 line; 44 px estimated height

solid-sizes (content=label|size=sm|variant=solid)

pass

content=label|size=sm|variant=solid

  • Evidence: mathematical
  • Coverage: complete (3 / 3 declared kinds evaluated)
  • Declared: contrast, target, layout
  • Evaluated: contrast, target, layout
  • Contrast: 76.6 Lc
  • Target: 44 px
  • Layout: root · wrap-anywhere 173 px single-line estimate; 68 px minimum width
    180 px: pass 1 line; 44 px estimated height240 px: pass 1 line; 44 px estimated height320 px: pass 1 line; 44 px estimated height

solid-sizes (content=label|size=lg|variant=solid)

pass

content=label|size=lg|variant=solid

  • Evidence: mathematical
  • Coverage: complete (3 / 3 declared kinds evaluated)
  • Declared: contrast, target, layout
  • Evaluated: contrast, target, layout
  • Contrast: 76.6 Lc
  • Target: 52 px
  • Layout: root · wrap-anywhere 230 px single-line estimate; 91 px minimum width
    180 px: pass 2 lines; 78 px estimated height240 px: pass 1 line; 52 px estimated height320 px: pass 1 line; 52 px estimated height

icon-md

pass

content=icon-only|size=md|variant=solid

  • Evidence: mathematical
  • Coverage: complete (3 / 3 declared kinds evaluated)
  • Declared: contrast, target, layout
  • Evaluated: contrast, target, layout
  • Contrast: 76.6 Lc
  • Target: 48 px
  • Layout: root · wrap-anywhere 18 px single-line estimate; 18 px minimum width
    180 px: pass 1 line; 48 px estimated height240 px: pass 1 line; 48 px estimated height320 px: pass 1 line; 48 px estimated height

icon-lg

pass

content=icon-only|size=lg|variant=solid

  • Evidence: mathematical
  • Coverage: complete (3 / 3 declared kinds evaluated)
  • Declared: contrast, target, layout
  • Evaluated: contrast, target, layout
  • Contrast: 76.6 Lc
  • Target: 52 px
  • Layout: root · wrap-anywhere 20 px single-line estimate; 20 px minimum width
    180 px: pass 1 line; 52 px estimated height240 px: pass 1 line; 52 px estimated height320 px: pass 1 line; 52 px estimated height

destructive-loading

pass

content=label|size=md|variant=destructive

  • Evidence: mathematical
  • Coverage: complete (3 / 3 declared kinds evaluated)
  • Declared: contrast, target, layout
  • Evaluated: contrast, target, layout
  • Contrast: 80.5 Lc
  • Target: 48 px
  • Layout: root · wrap-anywhere 143 px single-line estimate; 68 px minimum width
    180 px: pass 1 line; 48 px estimated height240 px: pass 1 line; 48 px estimated height320 px: pass 1 line; 48 px estimated height

link-anchor

pass

content=label|size=md|variant=link

  • Evidence: mathematical
  • Coverage: complete (3 / 3 declared kinds evaluated)
  • Declared: contrast, target, layout
  • Evaluated: contrast, target, layout
  • Contrast: 65.1 Lc
  • Target: 48 px
  • Layout: root · wrap-anywhere 233 px single-line estimate; 68 px minimum width
    180 px: pass 2 lines; 59 px estimated height240 px: pass 1 line; 48 px estimated height320 px: pass 1 line; 48 px estimated height