Interactive control for primary actions, form submission, and inline triggers.
Description
Button is the primary interactive control for triggering an action. It renders as a native <button> by default, composes leading and trailing icons as children alongside a <ButtonLabel>, supports a loading state with a built-in spinner, and an asChild prop for rendering as a link or any custom element via Radix Slot.
Reach for it whenever a user performs an action: submitting a form, opening a dialog, dismissing a toast, kicking off an async operation, or navigating between pages. The variant axis maps to product intents (primary for the main action, neutral-solid / neutral-light for secondary, danger for destructive, success for confirmation, inverse pair for dark surfaces). The appearance axis controls emphasis from solid down to ghost.
Don't use Button for non-action elements. If the trigger sits inside flowing prose and reads like part of the sentence, use TextLink. If a row of related actions belongs in a single visual cluster, use ButtonGroup. If it's a dedicated dismiss affordance on a dialog or toast, use CloseButton. If it carries metadata rather than triggering an action, use Badge.
Installation
Usage
Examples
Variants
variant sets the semantic color intent. Seven options cover the main action (primary), neutral surfaces (neutral-solid, neutral-light), destructive (danger), confirm (success), and the inverse pair for dark backgrounds (inverse-solid, inverse-light).
Appearance
appearance controls fill weight. solid (default) is high-contrast for the main action, outline adds a border with no fill, soft uses a tinted background, and ghost drops the surface entirely until hover.
Sizes
Five sizes scale from xs (20px tall) to xl (48px). lg is the default and pairs with body type.
Shape
shape controls the radius. rounded (default) follows the size scale, pill is fully rounded, square strips the radius for tabular or chrome layouts.
With Icon
Button has no icon props — compose icons as children and wrap the text in <ButtonLabel>. The icon renders as a sibling outside the label's optical padding; the component sizes it to match the active size and handles the gap. Place the icon before <ButtonLabel> for a leading icon, after it for a trailing one. A text-only button auto-wraps, so <ButtonLabel> is only needed when an icon sits beside the text.
Icon Only
iconOnly switches to a square footprint with no horizontal padding. Always pair it with an aria-label, since the icon alone is not announced by screen readers.
Loading
loading prepends a spinner before the label, sets aria-busy, and blocks pointer events so the action cannot fire twice. The label stays visible; the spinner color is picked to match the active variant and appearance. For iconOnly, the spinner replaces the icon.
As Child
asChild renders the Button styles around any child element via Radix Slot. Most commonly used to slot in an <a> (or framework Link) so a button-styled link still navigates.
Action bar
Buttons in a real settings surface: a destructive action on the left, and a primary Save (with its built-in loading state) beside a neutral Cancel on the right.
Accessibility
Button is a native <button> by default and inherits its implicit button role, focus ring, and activation keys. No manual ARIA wiring is needed for the default case.
ARIA notes:
loadingsetsaria-busy="true"on the root and disables pointer interaction; pair it with a visible label oraria-labelso the busy state is announced.iconOnlyrequires anaria-labelbecause the icon alone has no accessible name.- When
asChildis combined withdisabled, the rendered element is not a<button>and cannot use the nativedisabledattribute. The component switches toaria-disabled="true"instead. If you need the element fully inert, also remove it from the tab order (tabIndex={-1}).
Styling
Tailwind override: pass className to merge Tailwind classes with the component's CVA classes (via cn()):
Data slots and attributes: the component sets these for CSS targeting:
data-slot="button"on the root element.data-slot="button-label"on theButtonLabeltext wrapper.data-variant="<variant>",data-appearance="<appearance>",data-size="<size>"on the root.aria-busy="true"on the root whenloading.aria-disabled="true"on the root whenasChildis combined withdisabled.
Target a specific state in CSS:
Related Components
- Input Group: wraps Buttons with text inputs, selects, and addons inside a single input shell.
- Input: the text input primitive that Buttons most often submit or filter.
- Input Stepper: numeric input that exposes Button-styled increment and decrement triggers.
- Inline Alert: inline status block that often pairs with a Button as its dismiss or follow-up action.
API Reference
Button
Interactive control for triggering actions. Extends React.ComponentProps<"button">, so any standard button attribute (type, id, aria-*, onClick, etc.) is accepted.
Props
Variants
ButtonLabel
Wraps a Button's text label so its optical padding stays separate from sibling icons or other content. Plain-text children are wrapped automatically; use <ButtonLabel> explicitly whenever you compose an icon (or any element) next to the text. Extends React.ComponentProps<"span">.
Props
ButtonLabel reads its size from the parent Button automatically, so you normally don't set it.