Button
A button starts an action. One ink button carries the primary action on a view; everything else is secondary. Buttons rest on the page with an edge, and press in when you click.
Overview
Five looks, from loudest to quietest. Choose by how much the action matters on this view, not by how it should look.
Anatomy
Four parts. The edge and the label do the work; the icon is optional and the focus ring appears only for the keyboard.
- Container and edgeborder-edge with shadow-edge, 8px radius, 40px tall by default.
- LabelAsap 14/24 semibold, sentence case, a verb first.
- IconOptional, 16px, before the label. Decorative: aria-hidden.
- Focus ring2px cyan outline, offset 2px. Keyboard focus only.
Variants & sizes
Every state, every variant. Hover is a notch darker, press adds an inset, focus adds the cyan ring, disabled drops to half opacity.
| Variant | rest | hover | pressed | focus | disabled |
|---|---|---|---|---|---|
| Primary · default | |||||
| Secondary · outline | |||||
| Ghost | |||||
| Destructive | |||||
| Link |
Sizes
Icon only
Usage
One primary per view. Labels say what happens, in sentence case, starting with a verb.
Accessibility
- Role
- A real <button>, or a link rendered through render when it navigates. Never a div with a click handler.
- Name
- The visible label. Icon-only buttons need aria-label, and the icon stays aria-hidden.
- Keyboard
- Enter and Space activate. Focus shows the 2px cyan outline; never remove it.
- Contrast
- White on ink is 18.9:1 and ink on vellum 17.6:1. The edge is decoration; the label carries the meaning.
- Disabled
- Disabled buttons can’t take focus. When an action is blocked, say why next to it.
Props
| Prop | Type | Default | Notes |
|---|---|---|---|
variant | "default" | "secondary" | "outline" | "ghost" | "destructive" | "link" | "default" | outline and secondary draw the same button; both names stay for existing call sites. |
size | "default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg" | "default" | Heights 40, 24, 32 and 48. Icon sizes are square. |
render | ReactElement | — | Base UI: render as a link. Pass nativeButton={false} with an <a>. |
disabled | boolean | false | Half opacity and no pointer events. Prefer explaining why nearby. |
…button props | ComponentProps<'button'> | — | type, onClick, aria-* and the rest pass through. |
import { Button } from "@/components/ui/button"
<Button>Send from Revit</Button>
<Button variant="secondary" size="sm">Download</Button>
<Button nativeButton={false} render={<a href="/portal" />}>Open the Portal</Button>