Button
The primary action primitive: 5 variants × 3 sizes, all token-bound.
Maps 1:1 to the Figma Button component set. States (hover, focus, disabled) are handled entirely with CSS pseudo-classes, not extra props: what looks like a variant in Figma is a real interactive state in code.
Variants
Sizes
With icons
Loading
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | 'primary' | 'secondary' | 'outline' | 'ghost' | 'destructive' | 'primary' | Visual style of the button. |
| size | 'sm' | 'md' | 'lg' | 'md' | Height and padding scale. |
| leftIcon | ReactNode | Icon rendered before the label. | |
| rightIcon | ReactNode | Icon rendered after the label. | |
| loading | boolean | false | Shows a spinner and disables interaction. |
| disabled | boolean | false | Native disabled state. |
| ...rest | ButtonHTMLAttributes<HTMLButtonElement> | Every native <button> attribute (type, onClick, form…). |
Why it works this way
Why
- Five variants mirror the Figma set one-to-one, so a designer's choice maps to a prop with no translation.
- Hover, focus-visible and disabled are CSS states, not props: they are visual in Figma and semantic in code, and a prop would let them drift from the real interaction.
- A single filled primary marks the one action a view most wants. Two filled buttons compete, and neither reads as primary.
Don't
- Don't use it for navigation. A link goes somewhere, a button does something, and mixing them breaks keyboard and assistive-tech expectations. Use Link.
- Don't hand-style a <button> to look like one. You will miss hover, press, focus and loading behaviour, and every future improvement.
- Don't use it icon-only without a label. Use IconButton, which requires an aria-label.