React Button Variants, Explained: When to Use Each One
A practical guide to the eight button variants and twelve sizes in the registry โ what each one signals, when to use it, and how to handle loading, disabled, icon, and accessibility states.
A practical guide to the eight button variants and twelve sizes in the registry โ what each one signals, when to use it, and how to handle loading, disabled, icon, and accessibility states.
A button looks like the simplest component in your design system. It's also the one you'll render hundreds of times, in a dozen contexts, for actions of wildly different importance. That's how one-off button styles turn into a mess: six shades of green, three hover treatments, and a destructive action that looks exactly like the save button next to it.
A variant system fixes that with a small vocabulary. The registry's Button ships eight variants and twelve sizes, and each one answers a specific question. Here's when to reach for each.
Before variant specifics, the rule that matters most: a view should have one visually dominant action. Everything else is supporting. When three buttons compete for attention, users hesitate โ and the action they actually need gets buried.
The component takes three props that cover almost everything: variant, size, and asChild. Rank your actions, then map them:
The filled primary button. Use it for the single most important action in a view: "Start free trial," "Save changes," "Continue." If you're tempted to use it twice on one screen, decide which action is actually secondary.
A filled but quieter button. Use it beside a default button when both actions matter but one leads โ "Cancel" next to "Save," or "Book a demo" next to "Start free trial."
A bordered, transparent button. Good for toolbars, filters, and tertiary actions on tinted backgrounds, where a border is enough to establish affordance without adding another filled shape.
No background until hover. Use it for icon buttons in toolbars, row actions in tables, and navigation items. Ghost is also the right base inside dropdowns and menus, where a filled button would dominate.
Text that behaves like a button. Use it for inline actions inside prose ("Learn more") and never for primary page actions โ it reads as navigation, so users don't expect it to trigger work.
Reserved for irreversible actions: delete, revoke, disconnect. It's a visual warning, so don't reuse it for ordinary removals like "remove from cart," and always pair it with a confirmation dialog for anything destructive.
A tinted, branded treatment โ a primary-tinted background with a primary border, instead of a full fill. It draws the eye without shouting. Use it for upsells, "Upgrade" in a navigation bar, and high-value secondary actions in dashboards.
A translucent, blurred panel for buttons that sit on top of photography, gradients, or hero imagery. Use it sparingly: the backdrop blur needs contrast behind it, and it's a poor choice for dense interfaces or long labels.
Sizes map to interaction context far more than to aesthetics:
xs, mini-sm, mini, sm) through default and up to lg. Use default or lg for page-level CTAs and the compact sizes inside dense product UI.icon-xs through icon-lg) are square and sized for toolbars, close buttons, and list actions.pill-xs, pill-mini) are fully rounded for chips, filters, and chip-like actions.Touch targets are the constraint that overrides taste. On mobile, a 24px xs button is well under the roughly 44px minimum hit area. If a compact control matters on touch, step up to default or icon-lg, or add padding around it. Desktop-only toolbars can go smaller safely.
Disabled. Pass the standard disabled prop. The component removes pointer events and drops opacity to 50%, while keeping the button in the accessibility tree so screen readers can still announce it. Disable a control only when an action is genuinely unavailable โ if the real problem is discoverability, explain it instead.
Loading. There's no separate loading prop; the pattern is to disable the button and render a spinner next to or in place of the icon, keeping the label so the button doesn't change width mid-interaction:
<Button disabled={isSubmitting}>
{isSubmitting && <Loader2 className="animate-spin" />}
{isSubmitting ? "Saving..." : "Save changes"}
</Button>
Icons. Button sizes handle icon spacing automatically. Icon-only buttons should always carry an aria-label, because the SVG has no accessible text of its own:
<Button size="icon" aria-label="Close dialog">
<X />
</Button>
focus-visible ring. Don't remove the outline without replacing it with something equally visible.asChild for navigation. When a button is really a link, render a real anchor โ <Button asChild><a href="/pricing">Pricing</a></Button> โ so middle-click, keyboard navigation, and screen readers behave correctly.aria-invalid, so wire that from your form library instead of recoloring the button manually.<button> (the default) over a div with a click handler. It's focusable and activates on Enter and Space for free.pressed state and keeps aria-pressed correct.asChild as a link or use a small Button.| Scenario | Variant | Size |
|---|---|---|
| Page CTA | default | lg or default |
| Secondary CTA | outline or secondary | default |
| Nav action | ghost | default or sm |
| Inline text action | link | sm |
| Delete account | destructive | default |
| Upsell in nav | subtle | sm |
| Hero over image | glass | lg |
| Table row actions | ghost | icon-sm |
| Filter chips | outline | pill-xs |
| Form submit (loading) | default | default |
Variants aren't decoration โ they encode priority, and sizes encode context. Pick one primary action per view, rank everything else beneath it, and let the component enforce consistency. Browse the full component at Button to see all eight variants and twelve sizes rendered.
Get discovered by founders, earn honest verdicts, and grow your user base โ all for free.
Submit your productFAQ
A secondary button is filled with a muted background, while an outline button is transparent with a border. Both are supporting actions; secondary reads as slightly more prominent and works on plain backgrounds, while outline holds up better on tinted or dark surfaces.
One per view. A single default button keeps the primary action obvious. If several actions are equally important, they are not equally important โ pick the one that moves the user forward and demote the rest to secondary, outline, or ghost.
Use a Toggle when the control has a persistent on/off or selected state, such as bold text or a view switch. Use a Button when the control triggers an action. Toggle tracks the pressed state and exposes aria-pressed, which a Button does not.
Use the asChild prop and render a real anchor or framework Link as the child, for example <Button asChild><a href="/pricing">Pricing</a></Button>. This keeps link semantics, SEO, and middle-click behavior while preserving the button styling.