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-plus

layer

core / primitives

intensity

recedes · safe anywhere, including dense surfaces

composition

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 first

a11y

focus ring on --ring · loading sets aria-busy, not disabled · the label never emptiesjest-axe

dependencies

motion · class-variance-authority · Spinner from the same package
live demo · try it out
customize
variantvisual weight
sizedensity
shapepill fully rounds it
icon onlysquare, needs aria-label
loadingstill focusable
disabledprefer an inline error
usagecopy
import { Button } from "@usva-ui/react/primitives/button";

<Button>Save changes</Button>

props

proptypedefaultnotes
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.
asChildbooleanfalsemerges props onto the single child instead of rendering a <button>. how a link earns the button skin.
disabledbooleanfalsedims 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 / errorTextReactNodethe label beside the spinner, check and alert for each non-idle status.
iconOnlybooleanfalsea square button for one glyph. requires aria-label, it throws in dev without one.
tooltip / sideReactNode · "top" | "bottom" | "left" | "right"a visible label on hover and focus. what icon-only buttons use instead of text.
activebooleanfalsethe pressed look, for toggles.
settleDelaynumber1200how long success or error holds before returning to idle.
onSettle() => voidfires when a success or error settles back to idle.

get it

bun add @usva-ui/reactcopy
usagecopy
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>