# Shadcn Button

> Free shadcn/ui button component for React: solid, outline, ghost, link and destructive variants, plus icon and loading buttons. Copy or install in one command.

Source: https://designrevision.com/components/button

The **Shadcn Button** is the foundation of every action in your interface — a drop-in <a href="https://ui.shadcn.com/docs/components/button" rel="nofollow">shadcn/ui</a> button built on <a href="https://www.radix-ui.com/primitives/docs/utilities/slot" rel="nofollow">Radix</a> Slot and `class-variance-authority`, themed entirely with CSS variables. It ships six visual variants, seven size presets, ready-made icon and loading buttons, and an `asChild` escape hatch for rendering a link as a button.

## When to use

Use a button for **actions** the user takes — submitting a form, opening a dialog, triggering a mutation. For **navigation** between pages, prefer a link (the `link` variant, or `asChild` wrapping an anchor) so the markup stays semantic and accessible.

Match the variant to emphasis: **default** for the single most important action in a view, **secondary** or **outline** for supporting actions, **ghost** for low-emphasis toolbar actions, and **destructive** for irreversible operations like deleting.

## Button variants and sizes

Six variants cover the full emphasis scale: `default`, `secondary`, `outline`, `ghost`, `link`, and `destructive`. Seven sizes span `xs` through `lg`, plus three square icon sizes (`icon-sm`, `icon`, `icon-lg`) for icon-only buttons — set both with the `variant` and `size` props (see the Props panel in the Installation section for the full type signature). The button also auto-sizes any SVG child to 16px and spaces it from the label, so adding a `lucide-react` icon, a count `Badge`, or a `Kbd` shortcut hint needs no extra classes.

## Accessibility

Every button is keyboard-navigable with a visible focus ring and respects `prefers-reduced-motion`. Icon-only buttons must carry an `aria-label` so screen readers announce the action. Disabled buttons set the `disabled` attribute (not just a class), so they are removed from the tab order and announced correctly.

## Theming

The button reads your design tokens (`--primary`, `--secondary`, `--ring`, and friends), so it follows your theme — including dark mode — with zero overrides. Any palette produced by a shadcn-compatible theme generator applies automatically.

## Installation

### button

**Install:**

```bash
npx shadcn@latest add @designrevision/button
```

**Dependencies:** @radix-ui/react-slot, class-variance-authority, utils

**Props**

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| variant | 'default' \| 'destructive' \| 'outline' \| 'secondary' \| 'ghost' \| 'link' | default | Visual style of the button. |
| size | 'default' \| 'xs' \| 'sm' \| 'lg' \| 'icon' \| 'icon-sm' \| 'icon-lg' | default | Size preset. The "icon" sizes render square icon-only buttons. |
| asChild | boolean | false | Render as the child element via Radix Slot — e.g. to make a link look like a button. |

**Usage & accessibility:** Extends React.ButtonHTMLAttributes — all native <button> attributes are supported.

```tsx
// components/ui/button.tsx
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"

const buttonVariants = cva(
  "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
        destructive:
          "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
        outline:
          "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
        secondary:
          "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
        ghost: "hover:bg-accent hover:text-accent-foreground",
        link: "text-primary underline-offset-4 hover:underline",
      },
      size: {
        default: "h-9 px-4 py-2",
        xs: "h-7 gap-1.5 rounded-md px-2.5 text-xs",
        sm: "h-8 rounded-md px-3 text-xs",
        lg: "h-10 rounded-md px-8",
        icon: "size-9",
        "icon-sm": "size-8",
        "icon-lg": "size-10",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
)

export interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
  asChild?: boolean
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant, size, asChild = false, ...props }, ref) => {
    const Comp = asChild ? Slot : "button"
    return (
      <Comp
        className={cn(buttonVariants({ variant, size, className }))}
        ref={ref}
        {...props}
      />
    )
  }
)
Button.displayName = "Button"

export { Button, buttonVariants }
```

## Examples

### Simple

A versatile button with solid, ghost, outline and loading variants — keyboard-navigable and reduced-motion aware.

**Install:**

```bash
npx shadcn@latest add @designrevision/button-simple-01
```

**Dependencies:** button

```tsx
// components/ui/button-simple-01.tsx
"use client"

import { Button } from "@/components/ui/button"

export default function ButtonSimple01() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Button>Button</Button>
      <Button variant="secondary">Secondary</Button>
      <Button variant="outline">Outline</Button>
      <Button variant="ghost">Ghost</Button>
      <Button variant="destructive">Destructive</Button>
      <Button variant="link">Link</Button>
    </div>
  )
}
```

---

### With icon

Drop a `lucide-react` icon before the label — the button auto-sizes icons and spaces them, so no extra classes are needed.

**Install:**

```bash
npx shadcn@latest add @designrevision/button-icon-01
```

**Dependencies:** lucide-react, button

```tsx
// components/ui/button-icon-01.tsx
import { Download, Plus, Send } from "lucide-react"

import { Button } from "@/components/ui/button"

export default function ButtonIcon01() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Button>
        <Download />
        Download
      </Button>
      <Button variant="secondary">
        <Plus />
        New project
      </Button>
      <Button variant="outline">
        <Send />
        Send message
      </Button>
    </div>
  )
}
```

---

### With trailing icon

Place the icon after the label for forward actions like “Continue” or “Open”. Same auto-sizing and spacing.

**Install:**

```bash
npx shadcn@latest add @designrevision/button-icon-02
```

**Dependencies:** lucide-react, button

```tsx
// components/ui/button-icon-02.tsx
import { ArrowRight, ChevronRight, ExternalLink } from "lucide-react"

import { Button } from "@/components/ui/button"

export default function ButtonIcon02() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Button>
        Continue
        <ArrowRight />
      </Button>
      <Button variant="outline">
        Open docs
        <ExternalLink />
      </Button>
      <Button variant="ghost">
        Next
        <ChevronRight />
      </Button>
    </div>
  )
}
```

---

### Rounded

Add `rounded-full` for a pill shape. Works across every variant.

**Install:**

```bash
npx shadcn@latest add @designrevision/button-rounded-01
```

**Dependencies:** button

```tsx
// components/ui/button-rounded-01.tsx
import { Button } from "@/components/ui/button"

export default function ButtonRounded01() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Button className="rounded-full">Button</Button>
      <Button variant="secondary" className="rounded-full">
        Secondary
      </Button>
      <Button variant="outline" className="rounded-full">
        Outline
      </Button>
    </div>
  )
}
```

---

### Loading

Show an in-flight action with a spinning `Loader2` icon and `disabled` to block repeat clicks.

**Install:**

```bash
npx shadcn@latest add @designrevision/button-loading-01
```

**Dependencies:** lucide-react, button

```tsx
// components/ui/button-loading-01.tsx
import { Loader2 } from "lucide-react"

import { Button } from "@/components/ui/button"

export default function ButtonLoading01() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Button disabled>
        <Loader2 className="animate-spin" />
        Saving
      </Button>
      <Button variant="secondary" disabled>
        <Loader2 className="animate-spin" />
        Please wait
      </Button>
      <Button variant="outline" disabled>
        <Loader2 className="animate-spin" />
        Loading
      </Button>
    </div>
  )
}
```

---

### With count

Pair the label with a `Badge` to surface a count — unread, pending, or selected items.

**Install:**

```bash
npx shadcn@latest add @designrevision/button-count-01
```

**Dependencies:** lucide-react, button, badge

```tsx
// components/ui/button-count-01.tsx
import { Bell, Inbox, Mail } from "lucide-react"

import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"

export default function ButtonCount01() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Button>
        <Inbox />
        Inbox
        <Badge variant="secondary">24</Badge>
      </Button>
      <Button variant="secondary">
        <Mail />
        Messages
        <Badge>5</Badge>
      </Button>
      <Button variant="outline">
        <Bell />
        Alerts
        <Badge variant="destructive">9</Badge>
      </Button>
    </div>
  )
}
```

---

### With shortcut

Hint a keyboard shortcut with the `Kbd` component — common on search and command triggers.

**Install:**

```bash
npx shadcn@latest add @designrevision/button-kbd-01
```

**Dependencies:** lucide-react, button, kbd

```tsx
// components/ui/button-kbd-01.tsx
import { Search } from "lucide-react"

import { Button } from "@/components/ui/button"
import { Kbd } from "@/components/ui/kbd"

export default function ButtonKbd01() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Button variant="outline">
        <Search />
        Search
        <Kbd>⌘K</Kbd>
      </Button>
      <Button variant="secondary">
        Save
        <Kbd>⌘S</Kbd>
      </Button>
      <Button>
        Command menu
        <Kbd>⌘J</Kbd>
      </Button>
    </div>
  )
}
```

---

### Sizes

Four size presets — `xs`, `sm`, `default`, and `lg` — set via the `size` prop.

**Install:**

```bash
npx shadcn@latest add @designrevision/button-sizes-01
```

**Dependencies:** button

```tsx
// components/ui/button-sizes-01.tsx
import { Button } from "@/components/ui/button"

export default function ButtonSizes01() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Button size="xs">Extra small</Button>
      <Button size="sm">Small</Button>
      <Button size="default">Default</Button>
      <Button size="lg">Large</Button>
    </div>
  )
}
```

---

### Icon button

Square icon-only buttons with `size="icon"`. Always pass an `aria-label` for accessibility.

**Install:**

```bash
npx shadcn@latest add @designrevision/button-icon-only-01
```

**Dependencies:** lucide-react, button

```tsx
// components/ui/button-icon-only-01.tsx
import { Bell, Settings, Star } from "lucide-react"

import { Button } from "@/components/ui/button"

export default function ButtonIconOnly01() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Button size="icon" aria-label="Settings">
        <Settings />
      </Button>
      <Button variant="secondary" size="icon" aria-label="Favorite">
        <Star />
      </Button>
      <Button variant="outline" size="icon" aria-label="Notifications">
        <Bell />
      </Button>
    </div>
  )
}
```

---

### Icon button sizes

Icon buttons at the `icon-sm`, `icon`, and `icon-lg` presets.

**Install:**

```bash
npx shadcn@latest add @designrevision/button-icon-sizes-01
```

**Dependencies:** lucide-react, button

```tsx
// components/ui/button-icon-sizes-01.tsx
import { Plus } from "lucide-react"

import { Button } from "@/components/ui/button"

export default function ButtonIconSizes01() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Button variant="outline" size="icon-sm" aria-label="Add">
        <Plus />
      </Button>
      <Button variant="outline" size="icon" aria-label="Add">
        <Plus />
      </Button>
      <Button variant="outline" size="icon-lg" aria-label="Add">
        <Plus />
      </Button>
    </div>
  )
}
```

---

### Icon button with count

Overlay a `Badge` in the corner for a notification count.

**Install:**

```bash
npx shadcn@latest add @designrevision/button-icon-count-01
```

**Dependencies:** lucide-react, button, badge

```tsx
// components/ui/button-icon-count-01.tsx
import { Bell } from "lucide-react"

import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"

export default function ButtonIconCount01() {
  return (
    <div className="flex flex-wrap items-center gap-4">
      <div className="relative inline-flex">
        <Button variant="outline" size="icon" aria-label="Notifications">
          <Bell />
        </Button>
        <Badge className="absolute -right-1.5 -top-1.5 size-5 justify-center rounded-full p-0 tabular-nums">
          3
        </Badge>
      </div>
      <div className="relative inline-flex">
        <Button variant="outline" size="icon" aria-label="Notifications">
          <Bell />
        </Button>
        <Badge
          variant="destructive"
          className="absolute -right-1.5 -top-1.5 size-5 justify-center rounded-full p-0 tabular-nums"
        >
          9
        </Badge>
      </div>
    </div>
  )
}
```

---

### Icon button states

Rounded, loading, and disabled icon buttons.

**Install:**

```bash
npx shadcn@latest add @designrevision/button-icon-states-01
```

**Dependencies:** lucide-react, button

```tsx
// components/ui/button-icon-states-01.tsx
import { Heart, Loader2, Trash2 } from "lucide-react"

import { Button } from "@/components/ui/button"

export default function ButtonIconStates01() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Button
        variant="outline"
        size="icon"
        className="rounded-full"
        aria-label="Like"
      >
        <Heart />
      </Button>
      <Button variant="outline" size="icon" disabled aria-label="Loading">
        <Loader2 className="animate-spin" />
      </Button>
      <Button variant="outline" size="icon" disabled aria-label="Delete">
        <Trash2 />
      </Button>
    </div>
  )
}
```
