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.

Installation

1 Configure registry — once per project
{
  "registries": {
    "@designrevision": "https://registry.designrevision.com/r/{name}.json"
  }
}

Add to your existing components.json. Requires shadcn/ui v2.3+.

2 Install the component

Packages

@radix-ui/react-slot
class-variance-authority
utils

Props

variant = default
'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link'

Visual style of the button.

size = default
'default' | 'xs' | 'sm' | 'lg' | 'icon' | 'icon-sm' | 'icon-lg'

Size preset. The "icon" sizes render square icon-only buttons.

asChild = false
boolean

Render as the child element via Radix Slot — e.g. to make a link look like a button.

Extends React.ButtonHTMLAttributes — all native <button> attributes are supported.

Copied!
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.

Packages

button

Props

No props documented yet.

Copied!
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.

Packages

lucide-react
button

Props

No props documented yet.

Copied!
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.

Packages

lucide-react
button

Props

No props documented yet.

Copied!
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.

Packages

button

Props

No props documented yet.

Copied!
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.

Packages

lucide-react
button

Props

No props documented yet.

Copied!
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.

Packages

lucide-react
button
badge

Props

No props documented yet.

Copied!
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.

Packages

lucide-react
button
kbd

Props

No props documented yet.

Copied!
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.

Packages

button

Props

No props documented yet.

Copied!
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.

Packages

lucide-react
button

Props

No props documented yet.

Copied!
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.

Packages

lucide-react
button

Props

No props documented yet.

Copied!
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.

Packages

lucide-react
button
badge

Props

No props documented yet.

Copied!
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.

Packages

lucide-react
button

Props

No props documented yet.

Copied!
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>
  )
}

Frequently asked questions

It is built on the same Radix Slot and class-variance-authority foundation and the same CSS variable tokens, so it drops into any shadcn/ui project. On top of that it adds a visible focus ring, extra size presets (xs, icon-sm, icon-lg), and ready-made examples for icons, loading, counts, and shortcuts.
Place a lucide-react icon next to the label inside the button. The button auto-sizes icons to 16px and spaces them from the text, so you do not need any extra classes — put the icon before the label for a leading icon, or after it for a trailing one.
Use the icon, icon-sm, or icon-lg size for a square icon-only button, and always pass an aria-label so screen readers announce what the button does.
Yes. The button is styled with CSS variable tokens like --primary and --background, so it follows your theme automatically, including dark mode and any palette generated by a shadcn-compatible theme tool.
Render a spinning Loader2 icon inside the button and add the disabled attribute. Disabled blocks repeat clicks and removes the button from the tab order while the spinner signals that work is in progress.
Set asChild and wrap your own anchor or framework link component. The button styles are applied to that child element via Radix Slot, so it keeps its link semantics while looking like a button.