Button
The primary action control.
Renders a real `<button>`, so form submission, Enter and Space activation, and the disabled state come from the platform rather than from re-implemented event handlers. Five variants, three sizes, plus a loading state that keeps the button's width stable so surrounding content does not jump.
Import
import { Button } from "@abbainitiative/ui";Also available from the subpath entry @abbainitiative/ui/button.
This component carries its own "use client" directive. You can still render it from a Server Component — you simply cannot pass it a function prop, because functions do not serialise across the boundary.
Examples
Variants
<Inline gap={2}>
<Button>Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="danger">Danger</Button>
</Inline>Sizes
<Inline gap={2}>
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>
</Inline>States
The loading button keeps its label in the layout, so its width does not change.
<Inline gap={2}>
<Button loading>Saving</Button>
<Button disabled>Disabled</Button>
<Button asChild><a href="#button">As a link</a></Button>
</Inline>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | "primary" | "secondary" | "outline" | "ghost" | "danger" | "primary" | Visual weight. |
| size | "sm" | "md" | "lg" | "md" | Control height and typography. |
| loading | boolean | false | Shows a spinner, hides the label, and blocks interaction. |
| loadingLabel | string | "Loading" | Accessible description of what is loading. |
| asChild | boolean | false | Renders the single child element instead of a button, merging props onto it. Use for links styled as buttons. |
| leftIcon | ReactNode | Element before the label. Hidden from assistive technology. | |
| rightIcon | ReactNode | Element after the label. Hidden from assistive technology. | |
| fullWidth | boolean | false | Stretches to the width of its container. |
Accessibility
- Always a native button, so Enter and Space activation come from the browser.
- `loading` sets `aria-busy` and disables the control; the spinner carries an accessible label.
- Icons are wrapped in `aria-hidden`, keeping the accessible name to the label alone.
- With `asChild`, `aria-disabled` is used because anchors have no disabled attribute.
- Focus is shown with an `outline`, which does not shift layout the way a border would.