build69
build69  /  Components  /  Buttons & Actions  /  Button

Button

v1.0.14Updated 19 Oct 2026

Primary interactive element. Several size and variant styles, supports asChild for composition with Link components. Adds loading / success / error feedback states, an inline confirm flow, badge + tooltip slots, and an o

Preview

Live · 22 stories
DefaultOpen ↗
▸Why this is recommendedactionctaclickcontrol+4

The default, out-of-the-box Button.

Recommended for+14
  • ✓All click actions
  • ✓Form submission
  • ✓Toolbar actions
  • ✓Submit buttons with async work
  • ✓Destructive actions needing confirm
  • ✓A "?" inside the button that explains what the action does, without a second tab stop
  • ✓Explaining why a disabled button cannot be pressed
Tags+6
actionctaclickcontrolloadingconfirmrippledefault
Features+4
  • Tone (neutral / primary / success / warning / danger / info) × emphasis (solid / soft / outline / ghost / link)
  • Sizes: sm/default/lg/icon
  • asChild prop
  • States: idle / loading / success / error with auto reset
  • Inline confirm flow ("Are you sure?" inline reveal)
  • Badge slot (count / dot)
  • Tooltip slot (fires from anywhere on the button — usually spent on "why is this disabled"). While the button is disabled the trigger moves to a focusable wrapper span, so the explanation opens on hover AND on Tab (a disabled button holds no tab stop), with aria-describedby carrying the same text. Nothing becomes pressable: the button keeps its native disabled attribute
  • help slot: an inert "?" glyph INSIDE the button, after the label, explaining what the action DOES. Opens only from the glyph; aria-hidden and no tab stop (interactive content nested in a <button> is invalid HTML), so it never joins the accessible name; the button points at a hidden copy of it with aria-describedby (merged with any the caller passed), so focusing the button announces it. text-current + opacity, so it is legible on every tone × emphasis including solid primary. Stays hoverable while the button is disabled, and a tooltip-carrying disabled button keeps pointer events so it can still say why it is blocked. Ignored with asChild
  • Ripple on press (toggleable)
  • Async submit helper: awaits onClick, flips state
Not recommended for−2
  • ✕Navigation links inside body text (use a styled <a>)

Install

Ask your agent, or call the MCP tool directly. The resolved bundle is written into your project.

get_registry_item({ name: "Button" })

Usage

import { Button } from '@/components/primitives/button';

<Button state="loading" badge={3} tooltip="Send invoice">Send</Button>

Tick variants in the left menu — each adds a tab with that variant’s real story source, the same code the preview above renders.

Props

PropTypeRequired
tone

Semantic tone (what it means)

'neutral' | 'primary' | 'success' | 'warning' | 'danger' | 'info'—
emphasis

Visual emphasis (how loud)

'solid' | 'soft' | 'outline' | 'ghost' | 'link'—
size

Size preset

'xs' | 'sm' | 'md' | 'lg'—
iconOnly

Square icon-only button

boolean—
asChild

Render as the single child element

boolean—
state

Controlled feedback state

'idle' | 'loading' | 'success' | 'error'—
confirm

Inline confirm prompt

string | ButtonConfirm—
badge

Badge slot

ReactNode—
tooltip

Tooltip content, opened by hovering/focusing the whole button. While the button is disabled the trigger becomes a focusable wrapper span (data-slot="button-tooltip-trigger"), so hover AND Tab both reach the explanation and aria-describedby announces it; an enabled button keeps exactly the DOM it always had

ReactNode—
help

In-button "?" glyph after the label explaining what the action does. Opens only from the glyph (never from the button), aria-hidden + no tab stop, exposed to AT via aria-describedby on a hidden copy, inherits currentColor, hoverable while disabled. Ignored with asChild

ReactNode—
ripple

Enable ripple effect

boolean—

Every tonal component shares the same tone × emphasis API, so the pair looks identical across Button, Badge, Alert and more.

Related