docs / core / button
Button
the most-used thing in the system, so it does the least. one solid per region carries the action; everything else steps back. a button that performs is a button competing with the thing you clicked it for.
intensity · recedesrsc · client
provenance
shaped from personal-website · sisu-pluslayer
core / primitivesintensity
recedes · safe anywhere, including dense surfacescomposition
sits inside Card, PageHeader, Toolbar, Panel, Dialog footerasChild puts the skin on a link when the action navigatesnever inside another Button, never as a row's only click targetdanger styling is not a variant. destruction gets a Dialog firsta11y
focus ring on--ring · loading sets aria-busy, not disabled · the label never emptiesjest-axedependencies
motion · class-variance-authority · Spinner from the same packagelive demo · try it out
customize
variantvisual weight
sizedensity
shapepill fully rounds it
icon onlysquare, needs aria-label
loadingstill focusable
disabledprefer an inline error
usage
import { Button } from "@usva-ui/react/primitives/button";
<Button>Save changes</Button>props
| prop | type | default | notes |
|---|---|---|---|
| variant | "solid" | "soft" | "outline" | "ghost" | "onSurface" | "glass" | "solid" | visual weight. one solid per region. onSurface is a theme-tonal fill for a button on a surface or gradient you own; it adapts with the theme. glass is a fixed dark frost, blurred, for a control floating over a live atmosphere, where the theme cannot guarantee contrast. |
| size | "sm" | "md" | "lg" | "md" | sm exists for dense rows. it is a smaller button, not a subtler one. |
| shape | "rounded" | "pill" | "rounded" | pill fully rounds the button into a chip. the default follows the size radius. |
| asChild | boolean | false | merges props onto the single child instead of rendering a <button>. how a link earns the button skin. |
| disabled | boolean | false | dims to 50% and drops pointer events. loading is a status, not this. |
| status | "idle" | "loading" | "success" | "error" | "idle" | the content machine. keeps its width: loading swaps in the spinner, success and error flash their icon and settle back to idle. |
| loadingText / successText / errorText | ReactNode | — | the label beside the spinner, check and alert for each non-idle status. |
| iconOnly | boolean | false | a square button for one glyph. requires aria-label, it throws in dev without one. |
| tooltip / side | ReactNode · "top" | "bottom" | "left" | "right" | — | a visible label on hover and focus. what icon-only buttons use instead of text. |
| active | boolean | false | the pressed look, for toggles. |
| settleDelay | number | 1200 | how long success or error holds before returning to idle. |
| onSettle | () => void | — | fires when a success or error settles back to idle. |
get it
bun add @usva-ui/reactusage
import { Button } from "@usva-ui/react/primitives/button";
<Button variant="solid">Save changes</Button>
<Button variant="outline" size="sm">Cancel</Button>
<Button asChild>
<a href="/docs">Read the docs</a>
</Button>