Components
Button
Triggers an action or navigates to a new page. Available in six variants, three sizes, and supports loading and icon states out of the box.
Anatomy
Variants
Six variants cover every use case from primary CTAs to in-line link actions.
Sizes
Three sizes — use sm for compact UI, md for standard forms, and lg for marketing CTAs.
Loading state
Pass loading to replace the label with a spinning Loader2 icon and disable the button.
With icons
Use leftIcon or rightIcon for labeled buttons, and size="icon" for icon-only buttons.
Disabled
All variants respect the native disabled attribute, reducing opacity to 50% and removing pointer events.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'primary' | 'secondary' | 'ghost' | 'outline' | 'destructive' | 'link' | 'primary' | Visual style of the button. |
size | 'sm' | 'md' | 'lg' | 'icon' | 'md' | Controls height, padding, and font size. |
loading | boolean | false | Replaces content with a spinning loader and disables interaction. |
leftIcon | React.ReactNode | — | Icon rendered to the left of the label. |
rightIcon | React.ReactNode | — | Icon rendered to the right of the label. |
disabled | boolean | false | Disables the button and reduces opacity. |
className | string | — | Additional Tailwind classes to merge. |
...rest | ButtonHTMLAttributes | — | All native button props are forwarded. |
Usage rules
Do
- Use primary for the single most important action on a page.
- Use ghost for tertiary actions that should not compete visually.
- Always include an
aria-labelon icon-only buttons. - Use destructive only for irreversible actions like delete.
Don't
- Do not place two primary buttons side-by-side — only one primary per view.
- Do not use link variant as a navigation element inside prose.
- Do not mix icon-only and labeled buttons in the same button group.
- Do not use full-width buttons except in mobile or modal footers.
Accessibility
The component renders a native <button> element — keyboard focus, Enter, and Space activation are handled by the browser for free.
All variants include a focus-visible ring using the brand gold ring token — visible on keyboard nav, hidden on click.
Disabled state uses disabled not aria-disabled, so the element is correctly removed from the accessibility tree.
Loading state announces state change via the Loader icon — pair with a visually-hidden live region for critical async actions if needed.