Skip to content
Redpanda UI
Esc
↑↓navigate↵open⌘Jpreview
Made by shadcn
Edit on GitHub

Button

Displays a button or a component that looks like a button.

Powered by

Installation

bash
npx shadcn@latest add @redpanda/button
bash
pnpm dlx shadcn@latest add @redpanda/button
bash
yarn dlx shadcn@latest add @redpanda/button
bash
bunx shadcn@latest add @redpanda/button
bash
nubx shadcn@latest add @redpanda/button
bash
aube dlx shadcn@latest add @redpanda/button

Usage

tsx
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.

tsx
<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:

  1. A variant is named after the token it paints. primary paints --color-primary, warning paints the warning tone. No name means a different thing here than it does on a Badge.
  2. The neutral member is named after its family, not its tone. default, outline, ghost and dashed are each the neutral member of their family; all four paint secondary-*. Naming them after the tone would mean an unstyled control has to name one.
  3. The default is the neutral one. A plain <Button> is default, so primary is opt-in — an unstyled control should not claim the page’s one loud slot.
  4. 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, so success and informative are 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

  1. Filled — primary actions, highest emphasis
  2. Outline — secondary actions, medium emphasis
  3. 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. Use destructive-outline for 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 from surface-destructive on 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.

Use ButtonLink for native navigation with Button appearance:

tsx
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:

tsx
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 Button primitive, rendering a <button> unless render says otherwise
  • Styling — class-variance-authority, one variant × size pair 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-busy handled for you

Examples

Variants

Sizes

Dashed

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.

Recent changes

  • patchv1.2.0Pin shipped dependency floors to the version we develop against. Registry items now declare ranges like `^5.1.9` (the actual installed version) instead of collapsing to `^5.0.0`, so consumers start on the known-tested baseline while caret semantics still allow any compatible release within the same major.#133
  • minorv1.2.0Add `isLoading` prop to `Button`. Renders a centered spinner overlay while preserving the button's natural width, blocks interaction, and sets `aria-busy`. New playground/docs demos showcase the state across variants and sizes.#129
  • minorv1.1.0Theme docs refresh, readability pass on semantic foregrounds, and consumer-facing Base UI regression fixes.#121
  • minorv1.0.0Post-Base-UI polish. Public API unchanged.#116
  • majorv1.0.0Migrate every Radix-based primitive to `@base-ui/react@^1.4.0` (Base UI).#114
See full history →

Last updated on

Was this page helpful?