Button
Initiates an action, such as completing a task or submitting information.
Every Button states what it means: appearance (visual weight) and intent (tone) are required; there are no implicit defaults, so every call site declares both.
The primary action button should be appearance="filled" intent="neutral" — the system's default primary action. Reserve intent="accent" for deliberate brand emphasis.
Neutral
Accent
Danger
Size
Use the size prop to control the height and horizontal padding of the button, default md. The scale is shared with IconButton and SplitButton: the same size name always renders the same box height, so mixed compositions stay aligned.
Only the box scales: every size keeps the same typography (text-sm font-medium) and icon glyph size.
size has no effect when appearance="link": link buttons inherit the surrounding typography and have no box to size.
Type
Unlike the native <button> element (which defaults to type="submit"), a mantle Button defaults to type="button". This matches the wider React ecosystem (Radix, shadcn, MUI, …) and stops a button from accidentally submitting a surrounding <form> when all you wanted was an onClick.
The tradeoff: a button that should submit a form has to opt in with type="submit" (or type="reset" to reset it). A plain <Button> that relies on native form submission will silently do nothing.
When asChild is used, type has no effect and is not forwarded to the child, so a wrapped anchor never inherits a button type.
Icon and positioning
Use the icon prop to add an icon to the button. Never pass the icon as a child: that is an anti-pattern (see Common mistakes). By default, it will render on the logical start side of the button. Use the iconPlacement prop to change the side the icon is rendered on.
A button with an icon tightens the padding on the icon's side by 0.125rem, so the glyph reads optically aligned with the text block. That arrives as a ps-* or pe-* longhand, which a px-* override cannot beat: both survive the class merge, and Tailwind emits the longhand last. Override padding on a button with an icon in longhands too: className="ps-1.5 pe-1", not className="px-1.5".
Common mistakes
An icon-only button is not a Button: use IconButton instead. Button's icon prop places an icon beside its text children; render it with no children and the button has no accessible name (screen readers announce nothing) and keeps its text-box horizontal padding, so it draws as an off-square lozenge instead of a square. IconButton requires an accessible label, rendered as its aria-label, and renders a square box on the same shared size scale.
Never pass an icon as a child. A child icon is an anti-pattern: use the icon prop. The icon slot sizes the icon, holds it at shrink-0, tightens the padding on its side so it reads as optically aligned, and hands its place to the spinner when isLoading turns on. A child icon gets none of that.
The one exception is a second icon, such as a trailing caret, because the button has one icon slot. See Browser translation for the shape that stays safe on a translated page.
isLoading
isLoading sets whether the button is in a loading state, default false. Setting isLoading replaces any icon with a spinner, or adds one when the button has no icon. It also disables user interaction with the button and sets aria-disabled.
Idle
isLoading
The label slot
children render inside a <span data-slot="button-label">, beside the icon slot. The span exists so an appearing icon or spinner inserts before an element sibling: a browser translation engine reparents text nodes, and an insert aimed at one throws. Browser translation covers the mechanism and the rule your own components need.
The span carries display: contents, so it generates no box: every child stays a flex item of the button, and the button's gap still falls between them. Two children therefore lay out exactly as they did before the span existed.
Style the label by styling your own child, not the slot. A label that has to shrink inside a width-constrained button takes min-w-0 flex-1 truncate; those work because the child is a flex item of the button.
Button group
ButtonGroup is a contained group of related buttons. The "panel" appearance draws a bordered panel and sets --icon-button-border-radius to 0.125rem, so nested icon buttons square up to fit the chrome. The "ghost" appearance only spaces the buttons.
Polymorphism
To render something else as a Button, use the asChild prop to compose. This is useful when you want to splat the Button styling onto a react-router Link. When you use asChild, the Button does NOT pass its type prop to the child component.
A disabled or loading Button under asChild gets no disabled attribute, because the attribute is inert on an <a>. The child gets aria-disabled="true", leaves the tab order with tabIndex={-1}, and a click on it is cancelled before any handler or router navigation runs.
API Reference
Button
All props from button, plus:
Data attributes
ButtonGroup
A contained group of related buttons.
All props from div, plus: