Button

Triggers an action — save, submit, delete, open.

import { Button } from "@usefragments/ui";Source

Preview

<Button variant="solid">Save changes</Button>

Usage

Use when

  • Running an action: save, submit, delete
  • Submitting a form
  • Opening a dialog or menu

Avoid when

  • Plain navigation — use Link
  • Toggling state — use Switch or Checkbox
  • Picking an option — use Select or RadioGroup

Implementation notes

  • One solid accent button per form or section
  • tone="danger" for anything destructive
  • Chroma is earned: leave tone at its default unless the action carries that meaning
  • soft is the neutral secondary; soft and ghost show danger as ink, never a coloured fill
  • pending while work runs: it ignores presses without disabling the button
  • render={<a href />} for a link that looks like a button; the anchor keeps its link role
  • A button is as wide as its label in every parent; fullWidth fills a column, a grid cell or a row
  • Icon-only actions use IconButton

Do not use a Button for plain navigation.

Don’t

<Button href="/settings">Settings</Button>

Do

<Link href="/settings">Settings</Link>

Installation

pnpm add @usefragments/ui

Import styles once from @usefragments/ui/styles. See the installation guide for framework and theme setup.

Examples

Danger

Destructive action people cannot undo: solid chrome, danger tone.

<Button variant="solid" tone="danger">
  Delete item
</Button>

Sizes

The one track: xs 24 for in-row actions, sm 28, md 32, lg 40.

<Stack direction="row" gap="sm" align="center" wrap>
  <Button size="xs">Micro</Button>
  <Button size="sm">Small</Button>
  <Button size="md">Medium</Button>
  <Button size="lg">Large</Button>
</Stack>

Disabled

Blocks clicks and drops the button out of the tab order.

<Button disabled>Unavailable</Button>

Pending

Work is running: presses are ignored at once, the spinner shows after a second.

<Button pending>Saving</Button>

Render

render swaps the element; an anchor keeps its link role and takes the look.

<Button variant="soft" render={<a href="#billing-settings" />}>
  Billing settings
</Button>

Long Label

Long labels stay on one line; shorten the label or give the button room.

<Button>
  Save the localized account preferences and notify every affected workspace administrator
</Button>

API reference

Component props
Prop
Type
Default
Description
childrenReact.ReactNodeNot setThe label. Optional only so a Button can be the `render` element of a trigger (`<Dialog.Trigger render={<Button />}>Open</Dialog.Trigger>`), where the trigger's children become the label.
focusableWhenDisabledboolean | undefinedfalseKeep a disabled button in the tab order (`aria-disabled` instead of `disabled`), so keyboard and screen reader users can still reach it and read why it is unavailable.
fullWidthboolean | undefinedNot setFill the parent's width. Without it a button is as wide as its label in every parent, a column stack or a grid cell included.
nativeButtonboolean | undefinedNot setWhether the rendered element is a native `<button>`. Inferred from an element passed to `render` (a `<button>`, or a component without `href`); pass it when `render` is a function or a component that renders an anchor without an `href` prop.
pendingboolean | undefinedfalseWork started by this button is running. The button sets `aria-busy` and ignores further presses at once; after a second (`--fui-pending-delay`) its label fades and a spinner takes its place, and its width holds.
renderuseRender.RenderProp<Record<string, unknown>> | undefinedNot setReplace the rendered element, e.g. `render={<a href="/docs" />}` or a router link. A rendered anchor keeps its link semantics (no button role); the button's look, pending and disabled behaviour move onto it.
size"xs" | "sm" | "md" | "lg" | undefinedxssmmdlg"md"Control height on the one track: xs 24, sm 28, md 32, lg 40.
tone"danger" | "neutral" | "accent" | undefineddangerneutralaccentNot setColour. `solid` takes `accent` (default) or `danger`. Colour. `soft` and `ghost` take `neutral` (default) or `danger`, shown as ink. Colour. `link` takes `accent` (default, the link ink) or `neutral`.
variant"link" | "solid" | "soft" | "ghost" | undefinedlinksolidsoftghost"solid"Chrome family.

Accessibility

  • Label the action, not the widget
  • No "Click here"
  • Icon-only buttons need aria-label
  • LinkalternativeUse Link for navigation without action context
  • IconcomplementaryUse Icon inside Button for a leading or trailing glyph
  • ButtonGroupparentButtonGroup fuses a Button and an IconButton menu trigger into a split button
  • IconButtonsiblingThe one icon-only control

Next steps