Button
Button is the flagship interactive primitive — a labelled button with a three-axis style matrix, icon slots, and a loading state. It composes Pressable, so the accessibility contract comes for free.
What it is
A Pressable with a styled surface. Three props pick the look — variant (how heavy), intent
(which colour), size (how large) — and the component resolves the matching token bag. Caller
style props always override the matrix, so a one-off tweak does not need a new variant.
Beyond the matrix, Button adds composition slots (leadingIcon, trailingIcon) and a loading
state that suppresses clicks and sets aria-busy.
Install
Button is exported from usemotif. No separate install.
API
Button
stablefunction Button(props: ButtonProps): JSX.Element
Visual weight. `solid` is filled, `outline` is bordered, `ghost` reads as a tap target on hover only.
Semantic colour. Drives the `$colors.action.<intent>` token namespace.
Padding, font size, and radius shorthand.
Loading state. Suppresses clicks, sets `aria-busy`, and replaces `leadingIcon` with a loading indicator.
Content rendered before / after the label.
Overrides for the loading indicator and the visible label while `loading` is true.
Stretch to fill the parent's inline size.
Every Pressable prop — `onPress`, `disabled`, style props, pseudo-state bags.
Variants
The look is a matrix of three axes — variant × intent × size. The axes are independent:
any combination is valid, and defaultVariants mean <Button> with no props is a solid, primary,
medium button.
- variant sets the weight.
solidfills the background;outlinekeeps the border and drops the fill;ghostdrops both and reads as interactive only on hover. - intent sets the colour, pulling from
$colors.action.<intent>.primaryfor the main action,dangerfor destructive ones,successfor confirmations,neutralfor secondary actions. - size sets padding, font size, and radius together —
xsthroughxl.
Caller style props sit on top of the resolved matrix bag, so <Button intent="primary" bg="$colors.brand.500"> keeps the primary sizing and swaps only the background.
Accessibility
Button inherits Pressable's contract — it is a real <button>, focusable, keyboard-activated.
On top of that:
- The
loadingstate setsaria-busyand suppresses the click, so a double-submit cannot slip through while a request is in flight. The visible label can switch toloadingLabel, but the element stays the same button — focus is not lost. leadingIconandtrailingIconare decorative. They sit inside anaria-hiddenwrapper, since the label already carries the meaning. A button whose only content is an icon is the wrong tool — reach for IconButton, which requires anaria-label.
Examples
The default — solid, primary, medium:
A destructive action:
With a leading icon and a loading state:
A full-width button in a form: