Actions

Button

The primary action control.

Client component1 export

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.

As a link
<Inline gap={2}>
  <Button loading>Saving</Button>
  <Button disabled>Disabled</Button>
  <Button asChild><a href="#button">As a link</a></Button>
</Inline>

Props

Button props. All native attributes of the underlying element are also accepted and forwarded.
PropTypeDefaultDescription
variant"primary" | "secondary" | "outline" | "ghost" | "danger""primary"Visual weight.
size"sm" | "md" | "lg""md"Control height and typography.
loadingbooleanfalseShows a spinner, hides the label, and blocks interaction.
loadingLabelstring"Loading"Accessible description of what is loading.
asChildbooleanfalseRenders the single child element instead of a button, merging props onto it. Use for links styled as buttons.
leftIconReactNodeElement before the label. Hidden from assistive technology.
rightIconReactNodeElement after the label. Hidden from assistive technology.
fullWidthbooleanfalseStretches 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.