components / button

Button

Primary action control with compiled variants, size recipes, loading behavior, and proof-backed interaction states.

Overview

What it is

This section explains the intent of the component before the implementation details.

  • Button is the clearest expression of the DK component contract: one spec, one recipe pipeline, and one proof surface driving multiple interaction styles.
  • Use it for committed actions, secondary actions, destructive tasks, and button-like link flows where the control should still read as a button.

Aliases

cta, action button

Decision guide

How to choose it

Use these notes to decide quickly whether this is the right DK component for the job.

Decision guide

  • Choose Button when the interaction should feel deliberate, documented, and reusable across product surfaces.
  • If the scenario is more specialized than the current Button contract, prefer a simpler component or build a dedicated workflow on top of the lower-level DK primitives.

Do

  • Keep the Button label, value, or status language direct enough to scan quickly.
  • Use the documented size and state recipes instead of inventing one-off variants in product code.

Do not

  • Do not hide the main purpose of Button behind decorative copy or overloaded surface treatments.
  • Do not stretch Button into a workflow it was not designed to handle just because the visuals are close.

Usage

When to use

Prefer these situations when choosing this component.

  • Use Button for primary or secondary actions that need obvious affordance and consistent target sizing.
  • Use the link variant when the interaction should still behave like a button-shaped action rather than inline prose.
  • Use loading and disabled states to make action status visible instead of inventing custom spinners and opacity rules.

Usage

When not to use

These patterns are better served by a different component or a simpler surface.

  • Do not use Button for passive navigation lists or inline text links inside paragraphs.
  • Do not use icon-only buttons without a clear accessible label.
  • Do not create new visual button families when the existing variant contract already covers the intent.

Anti-patterns

  • Using Button as a generic layout container instead of a purpose-built interaction or content surface.
  • Forking the documented recipe in product code instead of extending the shared DK contract.

Migration notes

  • Migrate legacy button usage by mapping existing variants and states onto the documented DK props before porting any custom styling.
  • Treat the docs examples as the reference contract for accessibility and event behavior during adoption.

Anatomy

Structural parts

The anatomy explains which pieces matter to the recipe and accessibility model.

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.

API

Public interface

The docs contract distinguishes props, DOM events, and slots so integration behavior is explicit.

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.
hrefstringWhen 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.
ariaLabelstringRequired accessible label when `iconOnly` is true.
targetstringAnchor target when rendering as a link.
relstringAnchor relationship attributes when rendering as a link.
themeThemeContractOverrides the compiled DK theme used to resolve tokens and recipes for this component.

Slots

NameDescription
defaultPrimary visible label content.
leadingOptional icon or symbol before the label.
trailingOptional icon or symbol after the label.

Recipes

Variants, sizes, and states

These notes summarize the intended recipe surface rather than exposing raw implementation detail first.

Variants

  • solid: Highest-emphasis action for the main next step.
  • soft: Prominent but calmer secondary emphasis.
  • outline: Action with visible affordance and lighter fill.
  • ghost: Low-emphasis action for denser surfaces.
  • link: Button-like action styled with link energy while preserving a button frame.
  • destructive: High-clarity destructive action with stronger risk signaling.

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 and depth shift that strengthens affordance.
  • 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: Busy state that suppresses activation and surfaces progress.

Accessibility

Accessibility contract

This is the behavior the component promises to assistive tech and keyboard users today.

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.
  • Focus-visible styling appears without relying on hover-only affordance.

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 the visible label or title still produces a clear accessible name.
  • Verify focus remains obvious in every supported state and size.
  • Verify disabled, selected, and error states do not rely on color alone.

Implementation

Tokens and slot vars

This section shows the representative compiled slot variables that the runtime consumes for the selected design system.

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

  • Button remains the reference component for the recipe compiler and proof pipeline.
  • The slot-var section shows the resolved CSS variable contract the runtime consumes.

Implementation checklist

  • Use the public component API first and only drop to lower-level primitives when the component contract genuinely does not fit.
  • Keep theme overrides token-driven so proofs and recipes stay meaningful.
  • Verify the shipped examples and proof fixtures still represent the real product scenario you are implementing.

Examples

Copy-paste examples

Each example is intentionally practical, grouped by starter, common pattern, and edge-case coverage.

Examples
3
Depth
baseline

Starter

1 example

starter

Starter: primary action

The default solid medium button for the main next step.

Copy snippet

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

Common patterns

1 example

common

Common: destructive loading action

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

Copy snippet

<Button variant="destructive" loading={true}>Delete project</Button>
  • The loading state should suppress repeated activation.

Edge cases

1 example

edge

Edge: icon-only affordance

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

Copy snippet

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

Verification

Curated proof fixtures

Proofs stay visible in the docs so the system shows what it can guarantee, not just what it can render.

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

pass

content=label|size=md|variant=solid

  • Contrast: 76.6 Lc
  • Target: 48px
  • Layout: 173 / 180, 240, 320

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

pass

content=label|size=md|variant=soft

  • Contrast: 89.9 Lc
  • Target: 48px
  • Layout: 173 / 180, 240, 320

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

pass

content=label|size=md|variant=outline

  • Contrast: 65.1 Lc
  • Target: 48px
  • Layout: 173 / 180, 240, 320

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

pass

content=label|size=md|variant=ghost

  • Contrast: 65.1 Lc
  • Target: 48px
  • Layout: 173 / 180, 240, 320

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

pass

content=label|size=md|variant=link

  • Contrast: 65.1 Lc
  • Target: 48px
  • Layout: 173 / 180, 240, 320

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

pass

content=label|size=md|variant=destructive

  • Contrast: 80.5 Lc
  • Target: 48px
  • Layout: 173 / 180, 240, 320

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

pass

content=label|size=xs|variant=solid

  • Contrast: 76.6 Lc
  • Target: 44px
  • Layout: 150 / 180, 240, 320

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

pass

content=label|size=sm|variant=solid

  • Contrast: 76.6 Lc
  • Target: 44px
  • Layout: 173 / 180, 240, 320

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

pass

content=label|size=lg|variant=solid

  • Contrast: 76.6 Lc
  • Target: 52px
  • Layout: 230 / 180, 240, 320

icon-md

pass

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

  • Contrast: 76.6 Lc
  • Target: 48px
  • Layout: 18 / 180, 240, 320

icon-lg

pass

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

  • Contrast: 76.6 Lc
  • Target: 52px
  • Layout: 20 / 180, 240, 320

destructive-loading

pass

content=label|size=md|variant=destructive

  • Contrast: 80.5 Lc
  • Target: 48px
  • Layout: 143 / 180, 240, 320

link-anchor

pass

content=label|size=md|variant=link

  • Contrast: 65.1 Lc
  • Target: 48px
  • Layout: 233 / 180, 240, 320