Button
A <Button> is the primary primitive for any action that does one thing on click. Six visual variants, four height sizes, plus icon-only sizes. Composes Tooltip and disabledReason natively so disabled buttons can explain themselves.
When to use
- Every clickable thing that performs an action on the current page (Save, Delete, Create, Cancel).
- Form submission. Cancel actions in dialogs.
- Toolbar actions (use
size="sm"orsize="icon-sm").
Don’t use for navigation. A clickable thing that takes the user somewhere else is a link — use an <a> (or <Link>). Nav-as-button is bad for keyboard, screen readers, and middle-click “open in new tab”.
Variants
The variant carries the meaning. Don’t pick a variant by aesthetics.
<Button>Default</Button><Button variant="secondary">Secondary</Button><Button variant="outline">Outline</Button><Button variant="ghost">Ghost</Button><Button variant="destructive">Destructive</Button><Button variant="link">Link</Button>| Variant | Use |
|---|---|
default | The single most-important action on a screen. One per surface. |
secondary | The next-most-important action. Pair with default for two-action toolbars. |
outline | Neutral actions (“Cancel”, “Back”). Pair with default in dialogs. |
ghost | Toolbar / inline actions where chrome is expensive. |
destructive | Irreversible operations only. Always paired with a confirmation. |
link | Inline action inside body text. Don’t use as a primary CTA. |
Sizes
<Button size="lg">Large</Button><Button size="default">Default</Button><Button size="sm">Small</Button><Button size="xs">Extra small</Button>Icon-only
Use icon-only when the icon alone communicates the action (Tabler icons are unambiguous for most CRUD verbs). Always supply tooltip so the action has a name.
<Button size="icon" tooltip="Add row"><IconPlus /></Button><Button size="icon-sm" variant="ghost" tooltip="Open"><IconArrowRight /></Button><Button size="icon" variant="destructive" tooltip="Delete"><IconTrash /></Button>Icon + label
Leading icons reinforce intent; trailing icons indicate flow. Don’t put both.
<Button><IconPlus />Create deal</Button><Button variant="outline">Continue<IconArrowRight /></Button>Disabled with a reason
Disabled buttons fail an unstated test. Telling the user why turns a dead-end into guidance — set disabledReason and the tooltip surfaces only while disabled.
<Button disabled disabledReason="Fill in the agent name first.">Save</Button>Composing with tooltip
When the visible label doesn’t convey full intent (icon-only, abbreviated label, advanced action), pass tooltip. Don’t restate the visible label — that’s noise.
<Button variant="ghost" size="sm" tooltip="Re-run with same input">Retry</Button>API
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "default" | "secondary" | "outline" | "ghost" | "destructive" | "link" | "default" | Visual + semantic variant. |
size | "default" | "lg" | "sm" | "xs" | "icon" | "icon-lg" | "icon-sm" | "icon-xs" | "default" | Height + padding scale. Icon sizes are square. |
asChild | boolean | false | Render the button styling onto the child element (Radix Slot). Use when wrapping an <a> for true nav links. |
tooltip | ReactNode | — | Hover tooltip. Use only when the visible label is insufficient. |
disabledReason | ReactNode | — | Tooltip shown only while disabled, explaining why. Strongly recommended for any disabled state a user might want to un-disable. |
tooltipSide | "top" | "right" | "bottom" | "left" | "top" | Tooltip placement. |
disabled | boolean | false | Native disabled. When set with disabledReason, the button is wrapped to receive hover. |
onClick | (e: MouseEvent) => void | — | Native click handler. |
...rest | ButtonHTMLAttributes | — | All native <button> props are forwarded. |
Design guidelines
✓ Do
- Use one default-variant button per surface — it answers "what should I do here".
- Lead with the verb: "Create deal", "Save changes", "Delete agent". Sentence case.
- Pair destructive actions with a ConfirmDialog. Never one-click delete.
- Set disabledReason on every disabled button the user could un-disable.
✗ Don't
- Use a button for navigation — that is a link.
- Stack two default-variant buttons next to each other. Pick one primary, one secondary.
- Set a tooltip that just restates the visible label.
- Use destructive-variant for routine actions to "draw attention". The system has only one red.
Accessibility
- Native
<button>semantics —role="button"is implicit, no need to set. - Keyboard: Tab to focus, Space or Enter to activate. Browser-default.
- Focus ring: 3px brand-tinted ring via
focus-visible:. Visible on keyboard focus, hidden on click — not by us, by:focus-visiblethe spec. - Disabled buttons set
aria-disabledimplicitly via thedisabledattribute. - Icon-only buttons must set
tooltip(which doubles asaria-labelvia the tooltip’s text content). - The
disabledReasonwrapper setsaria-labelon the wrapping span when the reason is a string, so screen readers announce the reason.
Related
ActionButton— Button with mutation state (idle / pending / success / error). Use whenever the click triggers a fetch.ConfirmDialog— Required pairing for destructive actions.Tooltip— The underlying primitive. Use directly when the trigger isn’t a button.