Skip to content

Actions

Button

The system’s action, as a pill that does not move on hover.

When to reach for it

Anything that DOES something. If it navigates and looks like text, it is a link, not a ghost button.

Examples

variants

sizes

states

icon only

Notes

The system's action.

Renders a <button>, or an <a> when given href, or whatever you hand it via asChild. No router is imported, so the package stays framework-agnostic and a Next or React Router app wires its own Link at the call site.

Server-component friendly: there is no client boundary here, so it renders in a static page as well as an interactive one.

Props

Button props
PropTypeDefaultDescription
asChildbooleanfalseRender the child element instead of a `<button>`, keeping these styles. Use it to hand the styling to a router's `Link` — `asChild` is what keeps this package free of any one router. The decoration slots (`keycap`, `loading`) are not injected into a slotted child; compose them inside it yourself.
childrenReactNode
classNamestring
hrefstring
iconOnlybooleanfalseSquare control with no label. REQUIRES `aria-label`: an icon-only button with no accessible name is invisible to a screen reader, and this is the single most common way a design system ships an unusable control.
keycapstringOptional mono keycap glyph rendered after the label (e.g. "P").
loadingbooleanfalseSwaps the leading content for a spinner and blocks interaction. The label stays put — a button that empties out while it works loses its width, and the page reflows under the pointer that just clicked it.
sizeButtonSize'md'36 / 44 / 48px tall. `md` meets the pointer-target floor on its own.
variantButtonVariant'primary'Which action this is. `primary` is the one thing the screen wants you to do, so there is at most one per view; `danger` is reserved for destructive actions and is the only place chroma is allowed on a control.

Also accepts everything in AnchorHTMLAttributes<HTMLAnchorElement>, ButtonHTMLAttributes<HTMLButtonElement>. Those are forwarded to the underlying element and are not listed row by row.

Types

export type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger'
export type ButtonSize = 'sm' | 'md' | 'lg'

Accessibility

  • A native <button> by default, so Enter and Space both fire it.
  • loading sets aria-busy and disables the control; the label stays, so the box does not collapse under the pointer that just clicked it.
  • A link cannot be disabled, so href + loading sets aria-disabled and blocks pointer events instead.
  • iconOnly has no text, so it requires aria-label — the single most common way a design system ships an unusable control.