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
| Prop | Type | Default | Description |
|---|---|---|---|
| asChild | boolean | false | Render 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. |
| children | ReactNode | — | |
| className | string | — | |
| href | string | — | |
| iconOnly | boolean | false | Square 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. |
| keycap | string | Optional mono keycap glyph rendered after the label (e.g. "P"). | |
| loading | boolean | false | Swaps 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. |
| size | ButtonSize | 'md' | 36 / 44 / 48px tall. `md` meets the pointer-target floor on its own. |
| variant | ButtonVariant | '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.