# Button

A composed action primitive with a loading state, icon slots, semantic variants, and compact sizes.

> For the complete documentation index, see [llms.txt](/llms.txt). Markdown variants are available by appending `.md` to any URL or sending an `Accept: text/markdown` header. An agent skill is available at [/.well-known/agent-skills/site-skill.md](/.well-known/agent-skills/site-skill.md).





## Installation [#installation]

<CodeTabs>
  <TabsList>
    <TabsTrigger value="cli">
      Command
    </TabsTrigger>

    <TabsTrigger value="manual">
      Manual
    </TabsTrigger>
  </TabsList>

  <TabsContent value="cli">
    <CodeBlockCommand __bun__="bunx --bun shadcn@latest add https://lab.pratikthapw.dev/r/button.json" __npm__="npx shadcn@latest add https://lab.pratikthapw.dev/r/button.json" __pnpm__="pnpm dlx shadcn@latest add https://lab.pratikthapw.dev/r/button.json" __yarn__="npx shadcn@latest add https://lab.pratikthapw.dev/r/button.json" />
  </TabsContent>

  <TabsContent value="manual">
    <Steps className="mb-0 pt-2">
      <Step>
        Install the following dependencies:
      </Step>

      ```bash
      npm install @base-ui/react class-variance-authority clsx tailwind-merge
      ```

      <Step>
        Copy and paste the following code into your project.
      </Step>

      <ComponentSource name="button" title="components/ui/button.tsx" />

      <Step>
        Update the import paths to match your project setup.
      </Step>
    </Steps>
  </TabsContent>
</CodeTabs>

The composed API (`loading`, `swap`, `leftIcon`, `rightIcon`, and children with `data-icon`) is complete as installed; no extra pieces are required.

## Usage [#usage]

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

export function Actions() {
  return (
    <div className="flex flex-wrap gap-3">
      <Button>Save changes</Button>
      <Button variant="secondary">Preview</Button>
      <Button variant="outline">Cancel</Button>
      <Button variant="destructive">Delete</Button>
    </div>
  );
}
```

## Examples [#examples]

### Variants [#variants]

Use semantic variants to communicate action hierarchy. Avoid choosing a variant only for its color.

<ComponentPreview name="button-variants">
  <ButtonVariantsExample />
</ComponentPreview>

### Left icon [#left-icon]

`leftIcon` accepts any node and renders it in a dedicated `data-icon="inline-start"` slot, so spacing stays predictable. When `loading` is `true`, the spinner replaces the left icon in place, keeping width stable.

<ComponentPreview name="button-left-icon">
  <ButtonLeftIconExample />
</ComponentPreview>

```tsx
<Button leftIcon={<ArrowRightIcon />}>Continue</Button>
<Button leftIcon={<CheckIcon />} variant="outline">Approve</Button>
```

### Right icon [#right-icon]

`rightIcon` mirrors the left slot at `data-icon="inline-end"`. Use it for onward actions and dismissive affordances; the right slot never swaps for the loading spinner, so trailing icons stay visible while loading.

<ComponentPreview name="button-right-icon">
  <ButtonRightIconExample />
</ComponentPreview>

```tsx
<Button rightIcon={<ArrowRightIcon />}>Next step</Button>
<Button rightIcon={<BellIcon />} variant="outline">Notify me</Button>
```

### Loading [#loading]

`loading` disables the button and shows a spinner. When `leftIcon` is set, the spinner replaces it in place; otherwise it renders at inline-start.

<ComponentPreview name="button-loading">
  <ButtonLoadingExample />
</ComponentPreview>

```tsx
<Button loading>Saving changes</Button>
<Button leftIcon={<ArrowRightIcon />} loading>Continue</Button>
<Button aria-label="Loading" loading size="icon" variant="secondary" />
```

### Swap [#swap]

With no icons set, pass `swap` to stack the label and spinner in the same cell: the label becomes invisible and the spinner takes its place, so the button width never shifts mid-flight.

<ComponentPreview name="button-swap">
  <ButtonSwapExample />
</ComponentPreview>

```tsx
<Button loading={isPending} swap>
  Save changes
</Button>
```

`swap` only applies when neither `leftIcon` nor `rightIcon` is set; with icons present the in-place replacement already keeps width stable.

You can still place icons as ordinary children and tag them with `data-icon="inline-start"` or `data-icon="inline-end"`; the props above are sugar over that same contract.

## API [#api]

### Props [#props]

| Prop        | Type                                                                                 | Default     |
| ----------- | ------------------------------------------------------------------------------------ | ----------- |
| `variant`   | `"default" \| "secondary" \| "outline" \| "ghost" \| "destructive" \| "link"`        | `"default"` |
| `size`      | `"default" \| "xs" \| "sm" \| "lg" \| "icon" \| "icon-xs" \| "icon-sm" \| "icon-lg"` | `"default"` |
| `loading`   | `boolean`                                                                            | `false`     |
| `leftIcon`  | `React.ReactNode`                                                                    | None        |
| `rightIcon` | `React.ReactNode`                                                                    | None        |
| `swap`      | `boolean`                                                                            | `false`     |

When `loading` is `true`, the button is disabled and a private spinner renders: in place of `leftIcon` when set, inside the `swap` cell when `swap` is on, or at inline-start otherwise. Icon-only buttons still need an accessible name via `aria-label` or visually hidden text.

### Polymorphism [#polymorphism]

Button uses Base UI's [`render`](https://base-ui.com/react/components/button) prop and `nativeButton`. Pass `render={<a />}` only when the rendered element is not a native button, and set `nativeButton={false}` accordingly.

For links that look like buttons, prefer the exported `buttonVariants` helper with a plain `<a>` instead of rendering a link through the button. This keeps the semantic link role intact.

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

<a className={buttonVariants({ variant: "outline" })} href="/pricing">
  Pricing
</a>;
```
