# Button

A control that starts an action.

```tsx
import { Button } from 'ferry-ui'

export default function ButtonHero() {
  return (
    <>
      <Button variant="primary">Save changes</Button>
      <Button>Cancel</Button>
    </>
  )
}
```

## Usage guidelines

- **One primary action for each view.** Use `variant="primary"` for the main action. The other actions keep the `default` variant.
- **Confirm a destructive action.** A `destructive` button opens a [Confirm Dialog](/docs/components/confirm-dialog). It does not delete on a click.
- **A link is not a button.** For text that opens a page, use a link.
- **A state is not a button.** For an on/off state, use [Toggle](/docs/components/toggle) or [Switch](/docs/components/switch).

## Anatomy

Import the component. A button has one part.

```tsx title="Anatomy"

<Button />
```

## Examples

### Variants

The `variant` prop sets the look. Each look has one role.

```tsx
import { Button } from 'ferry-ui'

export default function ButtonVariants() {
  return (
    <>
      <Button>Default</Button>
      <Button variant="primary">Primary</Button>
      <Button variant="outline">Outline</Button>
      <Button variant="ghost">Ghost</Button>
      <Button variant="destructive">Destructive</Button>
      <Button variant="destructive-solid">Destructive solid</Button>
      <Button variant="warning">Warning</Button>
      <Button variant="link">Link</Button>
      <Button variant="dashed">Dashed</Button>
    </>
  )
}
```

| Variant | Role |
| --- | --- |
| `default` | A secondary action. |
| `primary` | The main action of the view. |
| `outline` | A secondary action on a tinted surface. |
| `ghost` | An action in a dense row or a toolbar. |
| `destructive` | The button that starts a destructive action. |
| `destructive-solid` | The confirm button of a destructive dialog. |
| `warning` | An action that needs attention. |
| `link` | An action that looks like a text link. |
| `dashed` | A filter button in a toolbar. |

### Sizes

The `size` prop sets the height. Use the same height as the fields near the button.

```tsx
import { Button } from 'ferry-ui'

export default function ButtonSizes() {
  return (
    <>
      <Button size="tiny">Tiny, 26px</Button>
      <Button size="sm">Small, 30px</Button>
      <Button size="md">Medium, 34px</Button>
      <Button size="lg">Large, 38px</Button>
    </>
  )
}
```

### Icons

Give a lucide icon to `icon` or to `iconRight`. The button sets the size of the icon.

A button with only an icon uses an `icon` size. It must have an `aria-label`.

```tsx
import { Button } from 'ferry-ui'
import { ArrowRight, Plus, Trash2 } from 'lucide-react'

export default function ButtonIcons() {
  return (
    <>
      <Button icon={<Plus />}>New project</Button>
      <Button iconRight={<ArrowRight />}>Continue</Button>
      <Button variant="ghost" size="icon" icon={<Trash2 />} aria-label="Delete the project" />
    </>
  )
}
```

### Loading state

Set `loading` while the action is in progress. The button shows a spinner and refuses clicks.

```tsx
import * as React from 'react'
import { Button } from 'ferry-ui'

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

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

  return (
    <Button variant="primary" loading={saving} onClick={save}>
      {saving ? 'Saving…' : 'Save changes'}
    </Button>
  )
}
```

### Link with the button look

With `asChild`, the button gives its look to its child element. Use it for a link that must look like a button.

```tsx
import { Button } from 'ferry-ui'
import { ExternalLink } from 'lucide-react'

export default function ButtonAsChild() {
  return (
    <Button asChild iconRight={<ExternalLink />}>
      <a href="https://github.com/Carter2307/ferry-ui" target="_blank" rel="noreferrer">
        Open the repository
      </a>
    </Button>
  )
}
```

## API reference

`Button` also accepts each attribute of the `<button>` element.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `"link" \| "default" \| "primary" \| "outline" \| "ghost" \| "destructive" \| "destructive-solid" \| "danger" \| "danger-solid" \| "warning" \| "dashed"` | `default` | Look. `default` (outlined neutral, the default), `primary` (solid: the one main action of a view), `outline` (transparent with a border, on tinted surfaces), `ghost` (borderless, dense rows and toolbars), `destructive` (red outline: starts a destructive action), `destructive-solid` (solid red: the confirm button of a destructive dialog only), `warning` (amber outline), `link` (text link look), `dashed` (filter buttons). `danger` and `danger-solid` are deprecated aliases of `destructive` and `destructive-solid`. |
| `size` | `"tiny" \| "sm" \| "md" \| "lg" \| "icon-tiny" \| "icon" \| "icon-md" \| "icon-lg"` | `sm` | Height. `tiny` 26px (dense inline actions), `sm` 30px (default: toolbars, table rows), `md` 34px (next to form fields), `lg` 38px. Square, icon-only sizes of the same heights: `icon-tiny` 26px, `icon` 30px, `icon-md` 34px, `icon-lg` 38px (they need an `aria-label`). |
| `shape` | `"default" \| "pill"` | `default` | Corners: `default` (6px radius) or `pill` (fully rounded: top-bar actions, search triggers). |
| `asChild` | `boolean` | `false` | Style the single child element (usually an `<a>` or a router link) as the button instead of rendering a `<button>`. `icon` / `iconRight` are rendered inside the child. A link has no native disabled state, so `disabled` and `loading` map to ARIA on the child: `aria-disabled` (dimmed, pointer events off), `tabIndex={-1}` and, for `loading`, `aria-busy` plus the spinner. `type` is ignored. |
| `loading` | `boolean` | `false` | Shows a spinner in place of `icon`, disables the button and sets `aria-busy`. Use it while the action triggered by this button is in flight. |
| `icon` | `ReactNode` |  | Leading icon (rendered before children). Pass a bare lucide icon: it is sized for you. |
| `iconRight` | `ReactNode` |  | Trailing icon (rendered after children). Pass a bare lucide icon: it is sized for you. |
