Button Atom
Four variants, six tones, three sizes — and a press animation that comes from the motion tokens, so it feels the same as every other press in the product.
Playground
variant
tone
size
<Button variant="solid" tone="brand" size="md"> Save changes</Button>Variants
<Button variant="solid">Save</Button><Button variant="soft">Save</Button><Button variant="outline">Save</Button><Button variant="ghost">Save</Button>Tone
<Button tone="danger">Delete</Button>Loading
loading disables the button, sets aria-busy, and swaps the leading icon for a spinner. The label
stays, so the button does not change width and the pointer does not land on something else.
<Button loading>Saving</Button>Do
Keep the label visible while loading, so the button holds its size and its meaning.
Don't
Replace the label with a bare spinner — the row reflows and the action becomes anonymous.
A link that looks like one
Button renders a <button> and only a <button>. Where the thing goes somewhere
rather than does something, reach for LinkButton — same classes, same tones and
sizes, but an anchor.
import { LinkButton } from '@ceebee/ui';
<LinkButton href="/places/12" variant="ghost" tone="neutral" size="sm">History</LinkButton>
// A router's own Link has to stay itself, or navigation stops being client-side:<LinkButton render={<Link href="/places/12" />} variant="ghost" iconStart={<History />} />They are two components because they are two contracts (ADR 0014). A link can be
opened in a new tab, copied, and reached from a screen reader's list of links; a
button can be disabled and can submit a form. The look is shared literally — the
same cb-button classes — so it cannot drift.
LinkButton is server-safe: an anchor needs no hook, no handler and no motion. It
does not animate on press, which is honest — a link does not depress, it goes
somewhere.
Props
| Prop | Type | Default | Notes |
|---|---|---|---|
| variant | 'solid' | 'soft' | 'outline' | 'ghost' | 'solid' | Visual weight. One solid button per view is usually enough. |
| tone | Tone | 'brand' | Semantic colour. |
| size | 'sm' | 'md' | 'lg' | 'md' | Control height token. |
| loading | boolean | false | Disables, sets aria-busy, shows a spinner in place of iconStart. |
| iconStart / iconEnd | ReactNode | — | Any icon element — the library never picks the icon set for you. |
| motion | boolean | true | Opts this button out of press feedback without touching MotionProvider. |
Motion redefines the drag and animation DOM events, so those handlers are not accepted here. A button that needs HTML5 drag is a different component.