Button

Button

Displays a button, built on Base UI. Eight variants and six sizes, each with a matching square icon-only size.

Playground

label
size
variant
icon
disabled
<Button>Button</Button>

Preview

variant sets the surface: solid default and destructive, soft secondary, bordered outline, bare ghost, and a text-only link. Two extra variants – inverted and inverted-ghost – are for sitting on dark or colored surfaces.

<Button>Default</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>

Installation

npx shadcn@latest add https://espresso-base-ui.vercel.app/r/button.json

Usage

import { Button } from "@/components/ui/button"
<Button variant="secondary" size="sm">
  Save changes
</Button>

Sizes

Six text sizes step from xs (24px) through default (32px) up to 2xl (52px), each with its own radius, typography and icon scale. The outline variant trims 2px off each height so its shadow ring lines up with siblings.

<Button size="xs">xs</Button>
<Button size="sm">sm</Button>
<Button size="default">Default</Button>
<Button size="lg">lg</Button>
<Button size="xl">xl</Button>
<Button size="2xl">2xl</Button>

Icons

Drop an svg next to the label and it is auto-sized for the button size; the square icon-* sizes make icon-only buttons – give those an aria-label.

<Button>
  <Mail /> Send Email
</Button>

<Button variant="outline">
  Continue <ChevronRight />
</Button>

<Button size="icon" aria-label="Add item">
  <Plus />
</Button>

<Button variant="outline" size="icon-sm" aria-label="Add item">
  <Plus />
</Button>

Disabled & loading

disabled mutes each variant's colors and blocks pointer events; for a loading state, disable the button and lead with a spinning icon.

<Button disabled>Default</Button>
<Button variant="secondary" disabled>
  Secondary
</Button>
<Button disabled>
  <LoaderCircle className="animate-spin" /> Loading...
</Button>

API reference

Button forwards everything to the Base UI Button primitive, so alongside the variant props you get disabled handling, the render prop for changing the underlying element, and nativeButton for non-button render targets – plus all standard button attributes.

Button

PropTypeDefaultDescription
variant"default" | "outline" | "secondary" | "ghost" | "destructive" | "link" | "inverted" | "inverted-ghost""default"Visual style. inverted and inverted-ghost read their colors from the --btn-inverted-* CSS variables for use on dark or colored surfaces.
size"xs" | "sm" | "default" | "lg" | "xl" | "2xl" | "icon-xs" | "icon-sm" | "icon" | "icon-lg" | "icon-xl" | "icon-2xl""default"Height, padding, radius, typography and icon scale. icon-* sizes are square, for icon-only buttons.
disabledbooleanfalseDisables the button – pointer events are blocked and each variant swaps to its muted disabled palette.
renderReactElement | (props, state) => ReactElement–Base UI render prop. Replaces the default <button> with your element (an <a>, a Next.js Link, ...) while keeping styling and behavior.

Styling hooks

The rendered element carries data-slot="button" and data-variant – compound components like ButtonGroup key their styles off these, and you can too. The buttonVariants cva helper is also exported for styling arbitrary elements as buttons:

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

<a href="#" className={cn(buttonVariants({ variant: "outline", size: "sm" }))}>
  Docs
</a>

Search documentation

Search the component docs