---
title: Button
description: A button component that can be rendered as another tag or focusable when disabled.
links:
  doc: https://base-ui.com/react/components/button
  anatomy: https://base-ui.com/react/components/button#anatomy
  api: https://base-ui.com/react/components/button#api-reference
---

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

export function ButtonDemo() {
  return <Button>Button</Button>;
}
```

## Installation

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

## Usage

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

```tsx
<Button>Button</Button>
```

## Cursor

**Tailwind v4** switched from `cursor: pointer` to `cursor: default` for the button component. [See reference](https://tailwindcss.com/docs/upgrade-guide#buttons-use-the-default-cursor)

If you'd like to continue using `cursor: pointer` by default, add these base styles to your CSS:

```css title="globals.css"
@layer base {
  button:not(:disabled),
  [role="button"]:not(:disabled) {
    cursor: pointer;
  }
}
```

## Examples

### Variants

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

export function ButtonDemo() {
  return (
    <div className="flex flex-col gap-3">
      <div className="flex flex-col gap-2 sm:flex-row">
        <Button variant="primary">Primary</Button>
        <Button variant="secondary">Secondary</Button>
        <Button variant="outline">Outline</Button>
        <Button variant="ghost">Ghost</Button>
        <Button variant="link">Link</Button>
      </div>
      <div className="flex flex-col gap-2 sm:flex-row">
        <Button variant="info">Info</Button>
        <Button variant="success">Success</Button>
        <Button variant="warning">Warning</Button>
        <Button variant="danger">Danger</Button>
        <Button variant="danger-soft">Danger Soft</Button>
      </div>
    </div>
  );
}
```

### Sizes

```tsx
import { IconTrash } from "@tabler/icons-react";

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

export function ButtonDemo() {
  return (
    <div className="space-y-3 *:grid *:grid-cols-[1fr_auto_1fr] *:justify-items-start *:gap-3">
      <div>
        <Button size="xs">Extra small</Button>
        <Button size="icon-xs">
          <IconTrash />
        </Button>
        <Button size="xs">
          <IconTrash />
          Extra small
        </Button>
      </div>
      <div>
        <Button size="sm">Small</Button>
        <Button size="icon-sm">
          <IconTrash />
        </Button>
        <Button size="sm">
          <IconTrash />
          Small
        </Button>
      </div>
      <div>
        <Button size="default">Default</Button>
        <Button size="icon">
          <IconTrash />
        </Button>
        <Button size="default">
          <IconTrash />
          Default
        </Button>
      </div>
      <div>
        <Button size="lg">Large</Button>
        <Button size="icon-lg">
          <IconTrash />
        </Button>
        <Button size="lg">
          <IconTrash />
          Large
        </Button>
      </div>
      <div>
        <Button size="xl">Extra large</Button>
        <Button size="icon-xl">
          <IconTrash />
        </Button>
        <Button size="xl">
          <IconTrash />
          Extra large
        </Button>
      </div>
    </div>
  );
}
```

### Render as another tag

The button can remain keyboard accessible while being rendered as another tag, such as a `<div>`, by specifying `nativeButton={false}`

The Button component enforces button semantics. `nativeButton={false}` signals that the rendered tag is not a `<button>`, but it must still be a tag that can receive button semantics (`role="button"`, keyboard interaction handlers).

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

export function ButtonDemo() {
  return (
    <Button nativeButton={false} render={<div />}>
      Click me
    </Button>
  );
}
```

### Rendering link as button

The Base UI Button component always applies `role="button"`, which overrides the semantic link role on `<a>` elements. Use `buttonVariants` with a plain `<a>` tag instead.

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

export function ButtonDemo() {
  return (
    <a href="#" className={cn(buttonVariants({ variant: "secondary", size: "lg" }))}>
      Login
    </a>
  );
}
```

### Loading state

For buttons that enter a loading state after being clicked, specify the `focusableWhenDisabled` prop to ensure focus remains on the button when it becomes disabled. This prevents focus from being lost and maintains the tab order.

```tsx
"use client";

import { useState } from "react";

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

export function ButtonDemo() {
  const [loading, setLoading] = useState(false);

  return (
    <Button
      disabled={loading}
      focusableWhenDisabled
      onClick={() => {
        setLoading(true);
        setTimeout(() => {
          setLoading(false);
        }, 3000);
      }}
    >
      {loading && <Spinner />}
      {loading ? "Submitting" : "Submit"}
    </Button>
  );
}
```

## API Reference

### Button props

<ApiReferenceAccordion
  type={{
    variant: {
      description: "The variant of the button.",
      type: "'primary' | 'outline' | 'ghost' | 'secondary' | 'link' | 'info' | 'success' | 'warning' | 'danger' | 'danger-soft'",
      default: `'primary'`,
    },
    size: {
      description: "The size of the button.",
      type: "'xs' | 'sm' | 'default' | 'lg' | 'xl' | 'icon-xs' | 'icon-sm' | 'icon' | 'icon-lg' | 'icon-xl'",
      default: `'default'`,
    },
  }}
/>
