Skip to content
Component

Button

Sizes sm/md/lg, five decorative tones, four appearances, start and end icons, full width, loading, a press ripple, and asChild for rendering as a link.

Tones

All five decorative tones, contained appearance. Button's tone is purely visual (ComponentTone) - it carries no status meaning, unlike Alert or Toast's semantic variant.

  • Fuji UI's native visual language treats default as the main action tone - it resolves to the --fuji-default token. Pass tone to switch to one of the other four decorative tones (fire, water, forest, sun) - purely a visual choice, not a status signal.

Sizes

sm / md / lg, default tone.

Appearance

contained (the raised accent), bordered, dashed, and ghost - the same four appearance names every control shares. ghost casts no shadow, so it sits flat on the surface.

With icons

startIcon and endIcon place an icon before or after the label, spaced by the button's own gap.

  • The label names the button, so the icon stays decorative - Lucide icons are aria-hidden unless you give them a label
  • While loading, the spinner takes startIcon's place and endIcon is hidden

Full width

fullWidth stretches the button to the width of its container, for a single form action or a stacked mobile layout.

Icon button

Icon-only buttons across colors; aria-label is required at the type level.

Loading

  • loading disables interaction and shows a spinner without changing the button's width

Disabled

Press ripple

A press grows a ripple from the point under the pointer. It holds while the pointer is down and fades on release, and each press gets its own wave.

  • On by default for Button and IconButton; ripple={false} turns it off for one control
  • Pointer presses only - Enter and Space activate the button without a wave
  • Skipped entirely under prefers-reduced-motion: reduce

As a link

asChild moves Button's styling onto its single child, so a navigation target keeps real link behavior - middle-click, open in a new tab - while looking like a button.

  • The child must forward its ref and spread the props it receives - a router's link component usually does
  • disabled and loading become aria-disabled plus a click and keyboard guard, since an anchor has no disabled attribute
  • The anchor does not pick up the browser's underline or link color

Button group

When to use

  • Triggering an action in place - submitting a form, opening a dialog, running a command.
  • Signalling hierarchy between actions on a surface: contained for the primary action, bordered for secondary, ghost for tertiary.

When not to use

  • Navigating to another page or URL - use Link (or Button asChild wrapping an anchor) so middle-click, open-in-new-tab, and right-click all work.
  • Toggling a single on/off value - use Switch or Checkbox, which announce their state.
  • An icon-only control - use IconButton, which requires an accessible name at the type level.

Keyboard

KeyAction
TabMoves focus to the button; a disabled or loading button is skipped.
Enter / SpaceActivates the button (fires onClick).

Accessibility notes

  • Every Button renders an explicit type (defaults to button, so it never submits a form by accident).
  • loading sets aria-busy and disables the control while the async action runs, and the label width is held so the layout doesn't jump.
  • Tone is decorative only - never rely on color alone to mark a destructive action; say so in the label.
  • The press ripple is pointer-only decoration and is skipped under prefers-reduced-motion: reduce.

Common mistakes

  • Using a Button for navigation - it breaks the browser affordances users expect from a link.
  • Leaving out type="submit" on the button that submits a form - Button defaults to type="button", so pressing it does not submit.

Known limitations

  • No built-in confirmation for destructive actions - pair it with an AlertDialog when the action can't be undone.

Related components

CriterionStatusNote
Keyboard supportSupportedFocusable in tab order; Enter and Space activate. Disabled/loading buttons leave the tab order.
Focus visibleSupportedA visible focus ring on keyboard focus via the shared focus-ring token.
Accessible nameSupportedText buttons are named by their content; icon-only actions use IconButton, whose aria-label is required at the type level.
Loading stateSupportedaria-busy plus disabled while loading, so assistive tech announces the pending state.
Reduced motionSupportedprefers-reduced-motion: reduce zeroes all Fuji transition durations globally.
Reduced transparencySupportedprefers-reduced-transparency: reduce flattens Glass to opaque surfaces (portals included).
RTL layoutPartialLogical flex/grid gap works under dir="rtl"; directional chevrons are not yet mirrored.

API reference

appearance"contained"
ComponentAppearance

Visual treatment - filled, outlined, dashed, or bare.

asChildfalse
boolean

Renders Button's styling onto its single child (ref- and prop-forwarding), e.g. a router link. Links lack native `disabled`, so `disabled`/`loading` use `aria-disabled` plus a click guard.

endIconNone
React.ReactNode

Icon rendered after the label.

fullWidthfalse
boolean

Stretches to the width of its container.

loadingfalse
boolean

Shows a spinner and disables interaction, keeping the button's width stable.

rippletrue
boolean

Plays a pointer-origin ripple on press. On by default, as part of how every button feels; pass `false` to suppress it. Does nothing under `prefers-reduced-motion: reduce`.

size"md"
ComponentSize

Control height and padding.

startIconNone
React.ReactNode

Icon rendered before the label.

tone"default"
ComponentTone

Decorative color.

Also accepts everything from React.ButtonHTMLAttributes<HTMLButtonElement>.

Coding agents read this reference, with its examples, from the version your project has installed, through the Fuji UI MCP server. Set up your agent