# Button

Starts an action, such as saving a form or opening a dialog.

- **Import:** `import { Button } from "@booleanpress/ui/button"`
- **Radix Slot:** <https://www.radix-ui.com/primitives/docs/utilities/slot>
- **APG Button:** <https://www.w3.org/WAI/ARIA/apg/patterns/button/>
- **Page:** <https://ui.booleanpress.com/components/button> · @booleanpress/ui 0.1.0

## Usage

Use a button for an action, and a link for a place. When a link should look like a button, pass `asChild` and the link: the button's classes move onto it.

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

export function Actions() {
  return (
    <div className="flex gap-2">
      <Button>Save</Button>
      <Button variant="outline">Cancel</Button>
    </div>
  )
}
```

`variant` sets the emphasis: one `default` button per area, `outline` or `ghost` for the others, `destructive` for an action that removes something. `size` keeps the density scale: 36 px by default, 32 px `sm`, 24 px `xs`, and square `icon` sizes for a button with an icon and no text.

## Examples

### Variants

Each emphasis, from the main action to a link.

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

export default function ButtonVariants() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Button>Save</Button>
      <Button variant="secondary">Duplicate</Button>
      <Button variant="outline">Cancel</Button>
      <Button variant="ghost">Skip</Button>
      <Button variant="destructive">Delete</Button>
      <Button variant="link">Learn more</Button>
    </div>
  )
}
```

### Sizes

The density scale, with and without text.

```tsx
import { PlusIcon } from "lucide-react"
import { Button } from "@booleanpress/ui/button"

export default function ButtonSizes() {
  return (
    <div className="flex flex-col gap-4">
      <div className="flex flex-wrap items-center gap-3">
        <Button size="xs">Extra small</Button>
        <Button size="sm">Small</Button>
        <Button>Default</Button>
        <Button size="lg">Large</Button>
      </div>
      <div className="flex flex-wrap items-center gap-3">
        <Button size="icon-xs" variant="outline" aria-label="Add a row">
          <PlusIcon />
        </Button>
        <Button size="icon-sm" variant="outline" aria-label="Add a filter">
          <PlusIcon />
        </Button>
        <Button size="icon" variant="outline" aria-label="Add a mailer">
          <PlusIcon />
        </Button>
        <Button size="icon-lg" variant="outline" aria-label="Add a contact">
          <PlusIcon />
        </Button>
      </div>
    </div>
  )
}
```

### With an icon

A 16 px icon before the text; an icon-only button names itself with `aria-label`.

```tsx
import { MailIcon, SettingsIcon } from "lucide-react"
import { Button } from "@booleanpress/ui/button"

export default function ButtonWithIcon() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Button>
        <MailIcon />
        Send a test email
      </Button>
      <Button variant="outline" size="icon-sm" aria-label="Settings">
        <SettingsIcon />
      </Button>
    </div>
  )
}
```

### Loading

A spinner replaces the icon, the button is disabled and keeps its width.

```tsx
import { useState } from "react"
import { SaveIcon } from "lucide-react"
import { Button } from "@booleanpress/ui/button"
import { Spinner } from "@booleanpress/ui/spinner"

export default function ButtonLoading() {
  const [saving, setSaving] = useState(false)

  function save() {
    setSaving(true)
    window.setTimeout(() => setSaving(false), 2000)
  }

  return (
    <Button onClick={save} disabled={saving}>
      {saving ? <Spinner /> : <SaveIcon />}
      Save changes
    </Button>
  )
}
```

### Disabled

A disabled button ignores clicks and leaves the tab order.

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

export default function ButtonDisabled() {
  return (
    <div className="flex flex-col items-start gap-1.5">
      <Button disabled>Publish</Button>
      <p className="text-sm text-muted-foreground">Add a sender address to publish.</p>
    </div>
  )
}
```

### As a link

`asChild` puts the button's look on a link, which keeps the link's role and address.

```tsx
import { ExternalLinkIcon } from "lucide-react"
import { Button } from "@booleanpress/ui/button"

export default function ButtonAsLink() {
  return (
    <Button asChild variant="outline">
      <a href="https://www.w3.org/WAI/ARIA/apg/patterns/button/">
        Read the button pattern
        <ExternalLinkIcon />
      </a>
    </Button>
  )
}
```

## Accessibility

**Semantics.** A native `button`, or the element you pass with `asChild` (keep a link a link).

**Labels.** Its text is its name. An icon-only button needs `aria-label`, and a tooltip with the same words helps sighted users.

**Focus.** It is in the tab order unless it is disabled. The focus ring shows on keyboard focus, not on click.

**Known limits.**

- A disabled button cannot be focused, so a tooltip on it cannot open. Say why it is disabled next to it.

### Keyboard

| Key | Behaviour |
| --- | --- |
| Enter | Activates the button. |
| Space | Activates the button. |

## API

### Button

Renders a `button` and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Render the child element instead, with this part's behaviour and classes merged onto it. |

**Also exported:** `buttonVariants`, the class names of Button's variants and sizes (`cva`), to give another element the same look.

Every part takes `className`, merged with its defaults by `cn()`, and `ref`, which reaches the element it renders.

**Data attributes:** `data-slot="button"` (Button), and `data-variant`, `data-size`.

## Theming

| Token | Used for |
| --- | --- |
| `--accent` | background |
| `--accent-foreground` | text |
| `--background` | background |
| `--destructive` | border, focus ring, background |
| `--input` | border, background |
| `--primary` | background, text |
| `--primary-foreground` | text |
| `--ring` | border, focus ring |
| `--secondary` | background |
| `--secondary-foreground` | text |
