---
title: Button
description: Displays a button or a component that looks like a button.
icon: mouse-pointer
author:
  name: shadcn
  url: https://github.com/shadcn
sidebar:
  badge: Updated
mode: wide
---

<PoweredBy packages={[{ name: 'Shadcn UI', url: 'https://ui.shadcn.com/docs/components/button' }]} />

### Installation

```package-install
npx shadcn@latest add @redpanda/button
```

<ComponentPreview name="button-demo" />

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

<ComponentPreview
  name="button-matrix-demo"
  description="Every button variant across default, hover, focus, active, disabled, and loading states."
/>

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

:::warning
**`primary` and `secondary` changed meaning, and seven spellings were removed.** These names used to
be emphasis ranks that painted the _opposite_ token: `variant="primary"` filled from
`--color-secondary`, and `secondary-outline` / `-ghost` / `-dashed` drew in `primary-*`. They now
paint what they are named after, matching Badge, the tokens and the shadcn baseline.

**A plain `<Button>` is unaffected** — the default moved from `primary` to `default`, which paints
the same neutral fill an unstyled button always had.

| Was                                                | Now                                              |
| -------------------------------------------------- | ------------------------------------------------ |
| `variant="primary"` (neutral fill)                 | `variant="default"`, or drop the prop            |
| `variant="secondary"` (action fill)                | `variant="primary"`                              |
| `secondary-outline` · `-ghost` · `-dashed`         | `primary-outline` · `-ghost` · `-dashed`         |
| `accent` · `accent-outline` · `-ghost` · `-dashed` | `brand` · `brand-outline` · `-ghost` · `-dashed` |
| `informative-*` · `success-*`                      | use a Badge or Alert; they are not action tones  |

:::

## 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

<ComponentPreview name="button-actions-crud-demo" description="CRUD action button composition patterns" />

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

<ComponentPreview name="button-actions-flow-demo" description="Form and multi-step flow button composition patterns" />

### 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

```mermaid
graph TD;
    START[What context?] --> CRUD{CRUD operation?};
    START --> FLOW{Form/Wizard/Flow?};
    START --> SINGLE{Single action?};

    CRUD -->|Create| C1[✅ primary filled];
    CRUD -->|Edit/Update| C2[✅ outline];
    CRUD -->|Delete| C3[✅ destructive-outline];
    CRUD -->|Confirm Delete| C4[✅ destructive filled];

    FLOW -->|Submit/Next/Continue| F1[✅ primary filled<br/>ONE per view];
    FLOW -->|Secondary action| F2[✅ outline];
    FLOW -->|Back/Cancel| F3[✅ ghost];

    SINGLE --> S1{Is it primary?};
    S1 -->|Yes - forward action| S2[✅ primary filled];
    S1 -->|No - check priority| S3{Priority level?};
    S3 -->|Secondary emphasis| S4[✅ outline];
    S3 -->|Tertiary subtle| S5[✅ ghost or link];
    S1 -->|Destructive action| S6{Needs confirmation?};
    S6 -->|Yes| S7[✅ destructive filled];
    S6 -->|No - inline delete| S8[✅ destructive-outline];

    RULE[⚠️ Content Rule] --> R1[Text OR Icon<br/>Never both];
    HIER[Visual Hierarchy] --> H1[1. Filled highest<br/>2. Outline medium<br/>3. Ghost lowest];

    classDef primary fill:#4ade80,stroke:#22c55e,stroke-width:3px,color:#000;
    classDef secondary fill:#93c5fd,stroke:#3b82f6,stroke-width:2px,color:#000;
    classDef tertiary fill:#d4d4d8,stroke:#a1a1aa,stroke-width:2px,color:#000;
    classDef destructiveFill fill:#fca5a5,stroke:#ef4444,stroke-width:3px,color:#000;
    classDef destructiveOutline fill:#fed7aa,stroke:#f97316,stroke-width:2px,color:#000;
    classDef decision fill:#60a5fa,stroke:#2563eb,stroke-width:2px,color:#fff;
    classDef rule fill:#fbbf24,stroke:#f59e0b,stroke-width:2px,color:#000;
    classDef context fill:#8b5cf6,stroke:#7c3aed,stroke-width:2px,color:#fff;

    class C1,F1,S2,S7 primary;
    class C2,C3,F2,S4,S8 secondary;
    class F3,S5 tertiary;
    class C4 destructiveFill;
    class C3,S8 destructiveOutline;
    class START,CRUD,FLOW,SINGLE,S1,S3,S6 decision;
    class RULE,R1 rule;
    class HIER,H1 context;
```

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

## Link

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](#content-guidelines))
- **Accessibility** — focus-visible ring, disabled and `aria-busy` handled for you

## Examples

### Variants

<ComponentPreview name="button-variants-demo" description="All button variants at a glance, grouped by emphasis" />

### Sizes

<ComponentPreview name="button-sizes-demo" description="Button sizes from xs to lg, with icons and icon-only" />

### Dashed

<ComponentPreview name="button-dashed-demo" description="The dashed variants, one per tone" />

### Link

<ComponentPreview name="button-link-demo" description="A button using the link variant." />

### Icon

<ComponentPreview name="button-icon-demo" description="An icon button" />

### With Icon

<ComponentPreview name="button-with-icon-demo" description="A button with an 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.

<ComponentPreview name="button-loading-demo" description="A button with a loading state. Click to trigger." />

<ComponentPreview name="button-loading-variants-demo" description="Loading state across button variants." />

<ComponentPreview
  name="button-loading-sizes-demo"
  description="Loading state across button sizes, including icon-only."
/>

### As Child

<ComponentPreview name="button-as-child-demo" description="A button wrapping a custom Link component" />

### Router Integration

<ComponentPreview
  name="button-router-integration-demo"
  description="Button with router integration using as and to props"
/>

### With keyboard shortcut

<ComponentPreview name="button-with-kbd-demo" description="Button with a trailing keyboard shortcut Kbd chip." />

### With status dot

<ComponentPreview
  name="button-with-status-dot-demo"
  description="Button with a leading animated status dot for online/offline presence."
/>

## Credits

- We take our inspiration from [Shadcn UI](https://ui.shadcn.com/docs/components/button) 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.
