Skip to content

Buttons

<button class="btn">Default</button>
<button class="btn btn-primary">Primary</button>
<button class="btn btn-ghost">Ghost</button>
<button class="btn btn-muted">Muted</button>
<button class="btn btn-danger">Danger</button>
<button class="btn btn-primary btn-sm">Small</button>
<button class="btn btn-primary">Medium</button>
<button class="btn btn-primary btn-lg">Large</button>
<button class="btn btn-primary" disabled>Disabled</button>
<button class="btn" disabled>Disabled</button>
<button
class="btn btn-primary btn-loading"
type="button"
disabled
aria-busy="true"
>
Saving
</button>
<button class="btn btn-loading" type="button" disabled aria-busy="true">
Loading
</button>
<button class="btn btn-primary btn-full-width">Continue</button>
<a href="/orders/new" class="btn btn-primary">New order</a>
<a href="/reports" class="btn">View reports</a>

disabled does nothing on an <a>. Omit the link or render plain text instead of styling a non-interactive link as disabled.

<button class="btn btn-primary">
<i class="ti ti-plus" aria-hidden="true"></i>
New order
</button>
<button class="btn">
Export
<i class="ti ti-download" aria-hidden="true"></i>
</button>
<button class="btn btn-danger">
<i class="ti ti-trash" aria-hidden="true"></i>
Delete
</button>
<button
class="btn btn-ghost btn-square"
type="button"
aria-label="More actions"
>
<i class="ti ti-dots-vertical" aria-hidden="true"></i>
</button>
<button class="btn btn-square btn-sm" type="button" aria-label="Edit">
<i class="ti ti-pencil" aria-hidden="true"></i>
</button>
<button
class="btn btn-danger btn-square btn-lg"
type="button"
aria-label="Delete"
>
<i class="ti ti-trash" aria-hidden="true"></i>
</button>
<button class="btn" type="button" aria-pressed="true">Auto-refresh</button>
<button class="btn" type="button" aria-pressed="false">Auto-refresh</button>
<ToggleButton hotkey="mod+e" defaultPressed>
Preview
</ToggleButton>
<Button
hotkey="mod+s"
icon={IconDeviceFloppy}
onClick={() => console.log("save")}
>
Save
</Button>
<Button hotkey={["mod+s", "mod+enter"]} onClick={() => console.log("save")}>
Save
</Button>
<div class="btn-group">
<button class="btn">Day</button>
<button class="btn">Week</button>
<button class="btn">Month</button>
</div>

A group is presentational — each child stays independently focusable, and nothing enforces a single selection. For single-select, use a boxed tab list as the segmented control.

<div class="btn-group btn-group-full-width">
<button class="btn">Day</button>
<button class="btn">Week</button>
<button class="btn">Month</button>
</div>
<div class="btn-group btn-group-vertical">
<button class="btn">Up</button>
<button class="btn">Center</button>
<button class="btn">Down</button>
</div>
<div class="btn-group btn-group-vertical">
<button class="btn btn-primary">
<i class="ti ti-pencil" aria-hidden="true"></i>
Edit
</button>
<button class="btn">
<i class="ti ti-copy" aria-hidden="true"></i>
Duplicate
</button>
<button class="btn">
<i class="ti ti-archive" aria-hidden="true"></i>
Archive
</button>
<button class="btn btn-danger">
<i class="ti ti-trash" aria-hidden="true"></i>
Delete
</button>
</div>
<div class="btn-group">
<button class="btn" type="button" aria-pressed="true">Stripes</button>
<button class="btn" type="button" aria-pressed="false">Compact</button>
<button class="btn" type="button" aria-pressed="false">Wrap text</button>
</div>
3
<div class="btn-group">
<button class="btn">Inbox</button>
<div class="indicator">
<span class="indicator-item badge badge-primary badge-sm">3</span>
<button class="btn">Messages</button>
</div>
<button class="btn">Archive</button>
</div>
Component Prop Type Default
Button variant "default" | "primary" | "ghost" | "muted" | "danger" "default"
Button size "sm" | "md" | "lg" "md"
Button fullWidth boolean false
Button loading boolean false
Button icon IconProp
Button iconTrailing IconProp
Button hotkey string | readonly string[]
ToggleButton pressed boolean
ToggleButton defaultPressed boolean false
ToggleButton onPressedChange (pressed: boolean) => void
ButtonGroup orientation "horizontal" | "vertical" "horizontal"
ButtonGroup fullWidth boolean false

type defaults to "button", so a button inside a form doesn’t submit unless you say so. loading implies disabled and aria-busy="true", and suppresses the leading icon while keeping a trailing one. A button with an icon and no children gets btn-square automatically.

hotkey dispatches a native click on the rendered element, so onClick fires, type="submit" submits, and render={<a href>} navigates; it also sets aria-keyshortcuts and renders a trailing Kbd chip. Pass an array for alternatives — only the first is shown. On a ToggleButton it flips the pressed state. For bindings not tied to a control, see Conventions › Hotkeys.

ToggleButton takes every Button prop except loading, plus Base UI’s pressed state; it wraps Base UI Toggle. Button wraps Base UI Button, so render and nativeButton come from there — pass both to render an anchor. Plus native <button> attributes, and <div> on ButtonGroup, which also defaults role="group".

Class Effect
btn 1rem/0.5rem padding, 0.5rem radius, text-sm medium, bordered muted surface, 0.5rem gap
btn-primary Brand fill, primary-content text, no border colour
btn-ghost No fill or border until hover
btn-muted Fills with the page surface so it sits flush rather than raised
btn-danger Danger fill, danger-content text
btn-sm text-xs, 0.75rem/0.375rem padding
btn-lg text-base, 1.25rem/0.625rem padding
btn-full-width width: 100%
btn-square Equalises the side padding for an icon-only button
btn-loading Dims, blocks pointer events, and paints a 1em spinner via ::before, hiding a leading icon
btn-group Joins its btn children into one strip: square inner corners, 1px overlap, currentColor seam
btn-group-vertical Stacks the strip instead, labels aligned to the start
btn-group-full-width Stretches the group; horizontal members split the row evenly

There is no btn-default or btn-md — both are the unmodified btn. The classes work on <a> as well as <button>.

aria-pressed is the toggle state, with no class involved: any btn carrying the attribute grows a leading mini switch, and "true" slides it across and adds the selected wash. Flip the attribute yourself. btn-loading doesn’t disable the button for keyboard users — pair it with the disabled attribute. btn-square needs an aria-label, since there’s no text to name it. Both spinners slow rather than stop under prefers-reduced-motion: reduce.

Group members can be a btn, an indicator wrapping one, or a menu for a split button — the seam and rounding rules drill through those wrappers. A badge on a middle member overhangs its neighbour, so set the indicator offset to 0 for a flush square corner.