# Button
URL: /design/components/button

The pressable object: thin, flat, and 8px round, in every weight a surface needs.

> For AI agents: a documentation index is available at [llms.txt](/llms.txt). Use `.md` for canonical markdown pages; `.mdx` is kept as a backwards-compatible alias on supported URL paths.

\[interactive preview component ButtonSpecimen omitted]

Code for ButtonSpecimen preview:

```tsx
"use client";

import { useState, type ReactNode } from "react";
import { Button } from "@/components/ui/button";

function ButtonSpecimen(): ReactNode {
  return (
    <Button>Read the docs</Button>
    <Button variant="outline">Outline</Button>
    <Button variant="secondary">Secondary</Button>
    <Button variant="ghost">Ghost</Button>
    <Button size="sm">Small</Button>
  );
}
```

## Installation

Install the dependencies:

- packages

  - @base-ui/react
  - class-variance-authority

Copy the source into `components/ui/button.tsx`. Registry items that depend on it install it automatically.

- code

  import { Button as ButtonPrimitive } from "@base-ui/react/button"; import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "@/lib/utils"; const buttonVariants = cva( "group/button focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:ring-1 active:not-aria-\[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:ring-1 \[&\_svg]:pointer-events-none \[&\_svg]:shrink-0 \[&\_svg:not(\[class\*='size-'])]:size-4", { variants: { variant: { default: "bg-primary text-primary-foreground hover:bg-primary/80", outline: "bg-muted/70 text-foreground hover:bg-muted aria-expanded:bg-muted aria-expanded:text-foreground dark:bg-muted/50 dark:hover:bg-muted border-transparent", secondary: "bg-secondary text-secondary-foreground aria-expanded:bg-secondary aria-expanded:text-secondary-foreground hover:bg-\[color-mix(in\_oklch,var(--secondary),var(--foreground)\_5%)]", ghost: "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50", destructive: "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40", link: "text-primary underline-offset-4 hover:underline", }, size: { default: "h-8 gap-1.5 px-3 has-data-\[icon=inline-end]:pr-2.5 has-data-\[icon=inline-start]:pl-2.5", xs: "h-6 gap-1 rounded-\[min(var(--radius-md),10px)] px-2 text-xs in-data-\[slot=button-group]:rounded-lg has-data-\[icon=inline-end]:pr-1.5 has-data-\[icon=inline-start]:pl-1.5 \[&\_svg:not(\[class\*='size-'])]:size-3", sm: "h-7 gap-1 rounded-\[min(var(--radius-md),12px)] px-2.5 text-\[0.8rem] in-data-\[slot=button-group]:rounded-lg has-data-\[icon=inline-end]:pr-1.5 has-data-\[icon=inline-start]:pl-1.5 \[&\_svg:not(\[class\*='size-'])]:size-3.5", lg: "h-9 gap-1.5 px-2.5 has-data-\[icon=inline-end]:pr-2 has-data-\[icon=inline-start]:pl-2", icon: "size-8", "icon-xs": "size-6 rounded-\[min(var(--radius-md),10px)] in-data-\[slot=button-group]:rounded-lg \[&\_svg:not(\[class\*='size-'])]:size-3", "icon-sm": "size-7 rounded-\[min(var(--radius-md),12px)] in-data-\[slot=button-group]:rounded-lg", "icon-lg": "size-9", }, }, defaultVariants: { variant: "default", size: "default", }, }, ); function Button({ className, variant = "default", size = "default", ...props }: ButtonPrimitive.Props & VariantProps\<typeof buttonVariants>) { return ( \<ButtonPrimitive data-slot="button" className={cn(buttonVariants({ variant, size, className }))} {...props} /> ); } export { Button, buttonVariants };

* title

  components/ui/button.tsx

* copyText

  import { Button as ButtonPrimitive } from "@base-ui/react/button"; import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "@/lib/utils"; const buttonVariants = cva( "group/button focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:ring-1 active:not-aria-\[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:ring-1 \[&\_svg]:pointer-events-none \[&\_svg]:shrink-0 \[&\_svg:not(\[class\*='size-'])]:size-4", { variants: { variant: { default: "bg-primary text-primary-foreground hover:bg-primary/80", outline: "bg-muted/70 text-foreground hover:bg-muted aria-expanded:bg-muted aria-expanded:text-foreground dark:bg-muted/50 dark:hover:bg-muted border-transparent", secondary: "bg-secondary text-secondary-foreground aria-expanded:bg-secondary aria-expanded:text-secondary-foreground hover:bg-\[color-mix(in\_oklch,var(--secondary),var(--foreground)\_5%)]", ghost: "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50", destructive: "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40", link: "text-primary underline-offset-4 hover:underline", }, size: { default: "h-8 gap-1.5 px-3 has-data-\[icon=inline-end]:pr-2.5 has-data-\[icon=inline-start]:pl-2.5", xs: "h-6 gap-1 rounded-\[min(var(--radius-md),10px)] px-2 text-xs in-data-\[slot=button-group]:rounded-lg has-data-\[icon=inline-end]:pr-1.5 has-data-\[icon=inline-start]:pl-1.5 \[&\_svg:not(\[class\*='size-'])]:size-3", sm: "h-7 gap-1 rounded-\[min(var(--radius-md),12px)] px-2.5 text-\[0.8rem] in-data-\[slot=button-group]:rounded-lg has-data-\[icon=inline-end]:pr-1.5 has-data-\[icon=inline-start]:pl-1.5 \[&\_svg:not(\[class\*='size-'])]:size-3.5", lg: "h-9 gap-1.5 px-2.5 has-data-\[icon=inline-end]:pr-2 has-data-\[icon=inline-start]:pl-2", icon: "size-8", "icon-xs": "size-6 rounded-\[min(var(--radius-md),10px)] in-data-\[slot=button-group]:rounded-lg \[&\_svg:not(\[class\*='size-'])]:size-3", "icon-sm": "size-7 rounded-\[min(var(--radius-md),12px)] in-data-\[slot=button-group]:rounded-lg", "icon-lg": "size-9", }, }, defaultVariants: { variant: "default", size: "default", }, }, ); function Button({ className, variant = "default", size = "default", ...props }: ButtonPrimitive.Props & VariantProps\<typeof buttonVariants>) { return ( \<ButtonPrimitive data-slot="button" className={cn(buttonVariants({ variant, size, className }))} {...props} /> ); } export { Button, buttonVariants };

* viewportClassName

  max-h-\[450px]

- language

  tsx

- code

  import { Button as ButtonPrimitive } from "@base-ui/react/button"; import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "@/lib/utils"; const buttonVariants = cva( "group/button focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:ring-1 active:not-aria-\[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:ring-1 \[&\_svg]:pointer-events-none \[&\_svg]:shrink-0 \[&\_svg:not(\[class\*='size-'])]:size-4", { variants: { variant: { default: "bg-primary text-primary-foreground hover:bg-primary/80", outline: "bg-muted/70 text-foreground hover:bg-muted aria-expanded:bg-muted aria-expanded:text-foreground dark:bg-muted/50 dark:hover:bg-muted border-transparent", secondary: "bg-secondary text-secondary-foreground aria-expanded:bg-secondary aria-expanded:text-secondary-foreground hover:bg-\[color-mix(in\_oklch,var(--secondary),var(--foreground)\_5%)]", ghost: "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50", destructive: "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40", link: "text-primary underline-offset-4 hover:underline", }, size: { default: "h-8 gap-1.5 px-3 has-data-\[icon=inline-end]:pr-2.5 has-data-\[icon=inline-start]:pl-2.5", xs: "h-6 gap-1 rounded-\[min(var(--radius-md),10px)] px-2 text-xs in-data-\[slot=button-group]:rounded-lg has-data-\[icon=inline-end]:pr-1.5 has-data-\[icon=inline-start]:pl-1.5 \[&\_svg:not(\[class\*='size-'])]:size-3", sm: "h-7 gap-1 rounded-\[min(var(--radius-md),12px)] px-2.5 text-\[0.8rem] in-data-\[slot=button-group]:rounded-lg has-data-\[icon=inline-end]:pr-1.5 has-data-\[icon=inline-start]:pl-1.5 \[&\_svg:not(\[class\*='size-'])]:size-3.5", lg: "h-9 gap-1.5 px-2.5 has-data-\[icon=inline-end]:pr-2 has-data-\[icon=inline-start]:pl-2", icon: "size-8", "icon-xs": "size-6 rounded-\[min(var(--radius-md),10px)] in-data-\[slot=button-group]:rounded-lg \[&\_svg:not(\[class\*='size-'])]:size-3", "icon-sm": "size-7 rounded-\[min(var(--radius-md),12px)] in-data-\[slot=button-group]:rounded-lg", "icon-lg": "size-9", }, }, defaultVariants: { variant: "default", size: "default", }, }, ); function Button({ className, variant = "default", size = "default", ...props }: ButtonPrimitive.Props & VariantProps\<typeof buttonVariants>) { return ( \<ButtonPrimitive data-slot="button" className={cn(buttonVariants({ variant, size, className }))} {...props} /> ); } export { Button, buttonVariants };

## Usage

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

export function Example() {
  return <Button variant="outline">Read the docs</Button>;
}
```

## Examples

### Sizes

The default height is 32px; `sm` is the 28px header control. `xs` and `lg` cover dense rails and prominent calls to action.

\[interactive preview component ButtonSizesSpecimen omitted]

Code for ButtonSizesSpecimen preview:

```tsx
"use client";

import { useState, type ReactNode } from "react";
import { Button } from "@/components/ui/button";

function ButtonSizesSpecimen(): ReactNode {
  return (
    <Button size="xs">Extra small</Button>
    <Button size="sm">Small</Button>
    <Button>Default</Button>
    <Button size="lg">Large</Button>
  );
}
```

### Icon buttons

The square `icon` sizes pair with an `aria-label`; an icon placed next to a label gets its spacing automatically.

\[interactive preview component ButtonIconSpecimen omitted]

Code for ButtonIconSpecimen preview:

```tsx
"use client";

import { useState, type ReactNode } from "react";
import {
  ChevronDownIcon,
  ChevronsUpDownIcon,
  CopyIcon,
  LogOutIcon,
  PlusIcon,
  RefreshCwIcon,
  SettingsIcon,
  UserIcon,
} from "lucide-react";
import { Button } from "@/components/ui/button";

function ButtonIconSpecimen(): ReactNode {
  return (
    <Button size="icon" aria-label="Settings">
      <SettingsIcon className="size-4" />
    </Button>
    <Button size="icon-sm" variant="outline" aria-label="Settings">
      <SettingsIcon className="size-3.5" />
    </Button>
    <Button size="icon-xs" variant="ghost" aria-label="Settings">
      <SettingsIcon className="size-3" />
    </Button>
    <Button variant="outline">
      <PlusIcon className="size-4" />
      New thread
    </Button>
  );
}
```

## API Reference

### Button

Extends the native `button` props through Base UI's `useRender`.

- `variant`: `"default" | "outline" | "secondary" | "ghost" | "destructive"` (default `"default"`) — The visual weight of the button.
- `size`: `"xs" | "sm" | "default" | "lg" | "icon" | "icon-sm" | "icon-xs"` (default `"default"`) — Height and padding; the icon sizes render a square button.
- `render?`: `ReactElement | function` — Compose as a different element, like a link.
- `className?`: `string` — Additional CSS classes.

### Style Variants (CVA)

| Export           | Description                      |
| ---------------- | -------------------------------- |
| `buttonVariants` | Styles for the button component. |

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

<a className={buttonVariants({ variant: "outline", size: "sm" })}>
  Styled link
</a>
```