Button
Displays a button, built on Base UI. Eight variants and six sizes, each with a matching square icon-only size.
Playground
<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.jsonUsage
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
| Prop | Type | Default | Description |
|---|---|---|---|
| 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. |
| disabled | boolean | false | Disables the button – pointer events are blocked and each variant swaps to its muted disabled palette. |
| render | ReactElement | (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>