GitHub

Button

The primary action component. Severity communicates the intent of the action — proceed, destroy, affirm — while variant controls the visual weight. Reach for Button whenever a user triggers an action; use a Link for navigation instead.

beta
tsx
import { Button } from "@idinstudio/ui";

Preview

Real, interactive examples rendered from the live component.

tsx
<Button severity="primary" size="lg">Create project</Button>
<Button variant="outline" severity="danger" iconLeft={<Trash2 />}>Delete</Button>
<Button variant="ghost" loading>Saving…</Button>

Props

Every styling and behavioural attribute is a typed prop — no className at the usage site.

PropTypeDefaultDescription
variant"default" | "outline" | "ghost" | "soft" | "link""default"Visual treatment. Default is the solid fill; link renders inline with no background.
severity"primary" | "secondary" | "danger" | "success" | "warning" | "neutral""primary"Communicates intent. Drives the colour pairing for every variant.
size"sm" | "md" | "lg""md"Control height and padding. md is the 44px-min touch target.
loadingbooleanfalseMarks an async action in progress. Suppresses clicks and sets aria-busy.
disabledbooleanfalseDisables the button and sets aria-disabled.
fullWidthbooleanfalseStretches the button to 100% of its container width.
iconLeftReact.ReactNodeLeading icon. Hidden while loading; marked aria-hidden.
iconRightReact.ReactNodeTrailing icon. Marked aria-hidden.
asChildbooleanfalseRenders children as the root element via Radix Slot — e.g. an anchor.
type"button" | "submit" | "reset""button"Native button type. Defaults to button to avoid accidental form submits.

Guidelines

When to reach for this component, and when not to.

Do

  • Use a verb-object label — "Create project", "Save changes".

  • Match severity to intent: danger for destructive, primary for the main path.

  • Keep one primary button per view so the main action stays obvious.

  • Set loading during async work so the button reflects progress and blocks double-submits.

Don't

  • Don't use a Button for navigation — use a Link, or asChild with an anchor.

  • Don't render an icon-only Button — use IconButton, which enforces aria-label.

  • Don't stack multiple primary buttons side by side; demote the rest to secondary or ghost.

  • Don't write vague labels like "OK", "Done", or "Click here".


Accessibility

The component's a11y contract. WCAG 2.1 AA is the non-negotiable baseline.

Keyboard

KeyAction
TabMove focus onto the button.
Shift + TabMove focus away from the button.
SpaceActivate the button.
EnterActivate the button.

ARIA & screen reader

Contract
  • Default render is a native <button type="button"> with implicit role button.
  • loading sets aria-busy="true" and data-state="loading"; the click handler is suppressed.
  • disabled sets aria-disabled="true" plus the native disabled attribute.
  • Leading and trailing icons are marked aria-hidden="true" so they do not double-announce.
  • Icon-only usage is not supported by Button — use IconButton, which requires aria-label.
  • All severity × variant pairs are validated against WCAG AA contrast in CI.