Button
Displays a button or a component that looks like a button.
Powered by
Installation
npx shadcn@latest add @redpanda/buttonpnpm dlx shadcn@latest add @redpanda/buttonyarn dlx shadcn@latest add @redpanda/buttonbunx shadcn@latest add @redpanda/buttonnubx shadcn@latest add @redpanda/buttonaube dlx shadcn@latest add @redpanda/buttonUsage
import { Button, ButtonLink } from "@/components/redpanda-ui/components/button"
<Button>Click me</Button><Button variant="outline">Outline</Button><Button variant="destructive">Delete</Button><Button size="sm">Small</Button><Button size="icon"><Plus /></Button>
// Keeps its width while a centred spinner overlays the content<Button isLoading>Save changes</Button>
// A destination needs native link semantics, not button keyboard behavior<ButtonLink href="/dashboard">Go to dashboard</ButtonLink>Typography and shape
Use finite appearance props rather than overriding the button’s classes. weight accepts
normal, medium, or semibold (the default). shape="pill" makes a rounded action or
circular icon control; the default retains ordinary or attached-group corners.
These props also work with buttonVariants.
<Button type="button" variant="outline" weight="normal" onClick={openCalendar}> Select date</Button><Button aria-label="Increase goal" shape="pill" size="icon-sm" type="button" onClick={increaseGoal}> <Plus /></Button>The default remains semibold with the same size, tone, and focus ring. Interaction transitions animate paint and scale, not layout dimensions.
Variants and States
Every variant across every state. The hover, focus and active columns are static copies of the real
state classes, so they can be compared without interacting; inverse and current-* sit on a filled
row, which is where they are meant to be used.
Tones
Four families — filled, outline, ghost, dashed — each in the same five tones. 25 variants, no two of them the same style. Four rules keep it that way:
- A variant is named after the token it paints.
primarypaints--color-primary,warningpaints the warning tone. No name means a different thing here than it does on a Badge. - The neutral member is named after its family, not its tone.
default,outline,ghostanddashedare each the neutral member of their family; all four paintsecondary-*. Naming them after the tone would mean an unstyled control has to name one. - The default is the neutral one. A plain
<Button>isdefault, soprimaryis opt-in — an unstyled control should not claim the page’s one loud slot. - Button carries action tones, Badge carries status tones. A tone belongs here only if you can
phrase an action in it —
brand(a brand CTA),warning(“proceed anyway”),destructive. You cannot ask someone to succeed, sosuccessandinformativeare Badge-and-Alert tones only.
Every family reads in the same order — neutral first, then the tones, then the one that borrows its ink from the ground:
| Family | Neutral | Action | Brand | Warning | Destructive | Borrows ink |
|---|---|---|---|---|---|---|
| Filled | default |
primary |
brand |
warning |
destructive |
inverse* |
| Outline | outline |
primary-outline |
brand-outline |
warning-outline |
destructive-outline |
current-outline |
| Ghost | ghost |
primary-ghost |
brand-ghost |
warning-ghost |
destructive-ghost |
current-ghost |
| Dashed | dashed |
primary-dashed |
brand-dashed |
warning-dashed |
destructive-dashed |
current-dashed |
* inverse flips its own fill and ink with the theme rather than borrowing the ground’s — it holds
the filled family’s last slot but is not a current-*.
Within a row the tones are interchangeable; across rows the emphasis drops. link sits outside the
grid: it is the action colour by definition and has no neutral counterpart.
Composing Buttons
The One Filled Button Rule
One filled button per screen or context. It is the action you want taken — submit, create, continue. More than one dilutes all of them.
Button Hierarchy
- Filled — primary actions, highest emphasis
- Outline — secondary actions, medium emphasis
- Ghost — tertiary actions, lowest emphasis
outline is the workhorse: its visible border reads as more clickable than a ghost while staying
clearly secondary to a fill.
CRUD Action Patterns
- Create — filled
primary - Edit/Update —
outline - Delete —
destructive-outline - Confirm delete — filled
destructive, in the confirmation dialog
Form and Multi-Step Flow Patterns
- Submit/Next/Continue — filled
primary, one per view - Secondary actions (save draft, skip) —
outline - Back/Cancel —
ghost
Escape routes stay quiet; the fill always means forward.
Content Guidelines
Text or an icon, never both. Icon-only buttons are for universally understood actions (close, search, menu) and always need an accessible label.
When to use
Variant Guidelines
default— the neutral filled button, and what a bare<Button>renders: the fallback when an action is neither the one forward step nor a supporting one.primary— forward-moving actions: submit, create, continue, save. One per context.outline— the neutral secondary button, and the one to reach for most: edit actions, secondary form actions, anything supporting that still needs to read as clickable.ghost— tertiary actions that shouldn’t dominate: back/cancel, icon buttons, and controls in data-dense surfaces like tables and navigation.dashed— “add new” and placeholder actions. Unshadowed, so it reads as an outline waiting to be filled rather than a raised control.destructive— permanent deletion, in confirmation dialogs only. Usedestructive-outlinefor inline deletes in tables, cards and lists.primary-*— the interactive-coloured member of any family. Reach for it when a secondary or tertiary action should read as coloured rather than neutral.brand-*— the logo red, for brand moments: marketing bands, onboarding, empty-state CTAs. Not a general-purpose emphasis: it sits ΔE 4.9 fromsurface-destructiveon a light page, so keep it out of rows that also hold a destructive action.warning-*— a caution action: “proceed anyway”, “override”, acknowledging a risky state. Same tone as a warning Badge or Alert, so it reads as part of that message rather than a second accent.current-*— takes the ink of whatever it sits on, for a surface the button cannot know about: a banner, a coloured panel.link— a text-link appearance. The variant does not change semantics: use Button for commands and ButtonLink for destinations.
Link
Use ButtonLink for native navigation with Button appearance:
import { ButtonLink } from '@/components/redpanda-ui/components/button';
<ButtonLink href="/login" variant="outline"> Log in</ButtonLink>;It requires href, forwards native anchor attributes and refs, and preserves native Enter,
modifier-click and Space scrolling. It accepts the finite variant, size, weight and
shape recipes, but not unstyled, null variants, render, as, disabled or isLoading.
If a destination is unavailable, render explanatory noninteractive content rather than a
link that falsely promises disabled button behavior.
For a router, keep the router’s actual Link and apply the public buttonVariants recipe:
import { buttonVariants } from '@/components/redpanda-ui/components/button/variants';
<Link to="/login" className={buttonVariants({ variant: 'outline' })}> Log in</Link>;Use the destination prop supported by your router (to or href). Do not render a
navigation link through Button: Base UI intentionally gives nonnative buttons Space
activation and a button role. Overriding only that role does not restore link keyboard
semantics. Button’s render remains available for actual action composition.
Anatomy
- Base — Base UI’s
Buttonprimitive, rendering a<button>unlessrendersays otherwise - Styling —
class-variance-authority, onevariant×sizepair per call - Content — text or an icon, gapped and auto-sized against the chosen
size(see Content Guidelines) - Accessibility — focus-visible ring, disabled and
aria-busyhandled for you
Examples
Variants
Sizes
Dashed
Link
Icon
With Icon
Loading
isLoading keeps the button’s width — the children stay in the layout, visually hidden, while a
centred spinner overlays them. Interaction is blocked and aria-busy is set.
As Child
Router Integration
With keyboard shortcut
With status dot
Credits
- We take our inspiration from Shadcn UI for the button component and style.
Disclosure values
For a compact code-like value that opens a disclosure, use variant="value", size="content", and weight="normal". The Button owns its monospace typography, quiet color, truncation, and focus ring; use layout classes only to constrain its available width.