Button
The primary way to commit an action. One primary per screen — everything else is secondary, ghost, or not a button at all.
Sizes#
Usage#
- One primary per screen. The primary button points at the single most important action. More than one dilutes the signal.
- Labels are 1–3 words, sentence case. “Buy direct”, “Sign in”, “Save changes”. No emojis. No all-caps.
- Ghost is not a decorative button. Use it for low-stakes actions inside a composed surface — “Cancel” in a dialog, “Collapse” on a section.
- Destructive always gets a confirmation step. Destructive-styled buttons never fire immediately; they open a confirm flow.
- Loading preserves width. The spinner sits inline so the button doesn't jump.
Props#
| Name | Type | Default | Description |
|---|---|---|---|
| variant | "primary" | "secondary" | "ghost" | "destructive" | "primary" | Visual weight. Primary for the one main action per screen. |
| size | "sm" | "md" | "lg" | "md" | Height and padding scale. |
| loading | boolean | false | Shows an inline spinner and disables the button. Preserves width. |
| disabled | boolean | false | HTML disabled; prevents pointer and keyboard activation. |
| asChild | boolean | false | When true, passes styling to the immediate child (use for wrapping <a> links). |
| children* | ReactNode | — | Button label. Keep to 1–3 words, sentence case. |
Code#
tsx
import { Button } from "@/components/ui/Button";
export function BuyDirect() {
return <Button>Buy direct</Button>;
}
// Variants
<Button variant="secondary">Learn more</Button>
<Button variant="ghost">Cancel</Button>
<Button variant="destructive">Remove</Button>
// States
<Button loading>Saving</Button>
<Button disabled>Out of stock</Button>
// Sizes
<Button size="sm">Small</Button>
<Button size="lg">Large</Button>Accessibility#
- Focus ring is the brand teal at 2 px offset. Never remove focus-visible styling.
loadingsetsaria-busyso screen readers announce the pending state.- Disabled buttons are not focusable and don't forward pointer events. For “not yet allowed” states, consider helper text instead of a disabled button.
- Labels work without color alone — the text itself communicates the action.