# Icon button

A square button with an icon and no text, which cannot be left without a name.

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

## Usage

`IconButton` is a `Button` with a square size and a required `label`: TypeScript refuses an icon button without one. The label becomes its accessible name, and with `tooltip` it shows on hover and keyboard focus too.

```tsx
import { IconButton } from "@booleanpress/ui/icon-button"
import { PencilIcon } from "lucide-react"

export function EditAction() {
  return (
    <IconButton label="Edit mailer" tooltip>
      <PencilIcon />
    </IconButton>
  )
}
```

The icon is its child. It takes Button's `variant` (`ghost` by default), `severity`, `rounded`, `raised`, `loading` and `disabled`. `size` is `xs` 24 px, `sm` 28 px, `default` 36 px or `lg` 42 px square, with 12, 12, 14 and 16 px icons; left out, it follows the provider's `controlSize`. `tooltipSide` moves the tooltip.

## Examples

### Basic

Text, outlined and danger icon buttons, each named by `label`.

```tsx
import { PencilIcon, SettingsIcon, TrashIcon } from "lucide-react"
import { IconButton } from "@booleanpress/ui/icon-button"

export default function IconButtonBasic() {
  return (
    <div className="flex gap-2">
      <IconButton label="Edit mailer">
        <PencilIcon />
      </IconButton>
      <IconButton label="Mailer settings" variant="outline">
        <SettingsIcon />
      </IconButton>
      <IconButton label="Delete mailer" variant="default" severity="danger">
        <TrashIcon />
      </IconButton>
    </div>
  )
}
```

### Severities and variants

Each severity as a solid, outlined and text icon button.

```tsx
import { BellIcon, CheckIcon, HeartIcon, InfoIcon, SearchIcon, TriangleAlertIcon, XIcon } from "lucide-react"
import { IconButton } from "@booleanpress/ui/icon-button"

const SEVERITIES = [
  { severity: undefined, label: "Search", icon: <SearchIcon /> },
  { severity: "success", label: "Approve", icon: <CheckIcon /> },
  { severity: "info", label: "Details", icon: <InfoIcon /> },
  { severity: "warning", label: "Notifications", icon: <BellIcon /> },
  { severity: "help", label: "Favourite", icon: <HeartIcon /> },
  { severity: "danger", label: "Reject", icon: <XIcon /> },
  { severity: "contrast", label: "Warnings", icon: <TriangleAlertIcon /> },
] as const

export default function IconButtonSeverities() {
  return (
    <div className="flex flex-col gap-3">
      {(["default", "outline", "ghost"] as const).map((variant) => (
        <div key={variant} className="flex flex-wrap gap-2">
          {SEVERITIES.map(({ severity, label, icon }) => (
            <IconButton key={label} label={`${label} (${variant})`} variant={variant} severity={severity}>
              {icon}
            </IconButton>
          ))}
        </div>
      ))}
    </div>
  )
}
```

### Sizes

`xs`, `sm`, `default` and `lg`: 24, 28, 36 and 42 px.

```tsx
import { RefreshCwIcon } from "lucide-react"
import { IconButton } from "@booleanpress/ui/icon-button"

export default function IconButtonSizes() {
  return (
    <div className="flex items-center gap-2">
      {(["xs", "sm", "default", "lg"] as const).map((size) => (
        <IconButton key={size} label={`Refresh (${size})`} size={size} variant="outline">
          <RefreshCwIcon />
        </IconButton>
      ))}
    </div>
  )
}
```

### With tooltip

`tooltip` shows the label on hover and focus; `tooltipSide` places it.

```tsx
import { CopyIcon, DownloadIcon, ShareIcon } from "lucide-react"
import { IconButton } from "@booleanpress/ui/icon-button"

export default function IconButtonWithTooltip() {
  return (
    <div className="flex gap-2">
      <IconButton label="Copy message ID" tooltip>
        <CopyIcon />
      </IconButton>
      <IconButton label="Download the report" tooltip tooltipSide="bottom">
        <DownloadIcon />
      </IconButton>
      <IconButton label="Share with the organisation" tooltip tooltipSide="right">
        <ShareIcon />
      </IconButton>
    </div>
  )
}
```

### Rounded

`rounded` makes a circle; `raised` adds the shadow.

```tsx
import { CheckIcon, PlusIcon, XIcon } from "lucide-react"
import { IconButton } from "@booleanpress/ui/icon-button"

export default function IconButtonRounded() {
  return (
    <div className="flex gap-2">
      <IconButton label="Add recipient" rounded variant="default">
        <PlusIcon />
      </IconButton>
      <IconButton label="Accept" rounded variant="outline" severity="success">
        <CheckIcon />
      </IconButton>
      <IconButton label="Decline" rounded variant="default" severity="danger" raised>
        <XIcon />
      </IconButton>
    </div>
  )
}
```

### Loading

`loading` puts the spinner in place of the icon and refuses presses while the work runs; the button keeps its focus.

```tsx
import * as React from "react"
import { RefreshCwIcon } from "lucide-react"
import { IconButton } from "@booleanpress/ui/icon-button"

export default function IconButtonLoading() {
  const [loading, setLoading] = React.useState(false)

  const refresh = () => {
    setLoading(true)
    window.setTimeout(() => setLoading(false), 1500)
  }

  return (
    <div className="flex gap-2">
      <IconButton label="Refresh the log" variant="outline" loading={loading} onClick={refresh}>
        <RefreshCwIcon />
      </IconButton>
      <IconButton label="Refreshing" variant="default" loading>
        <RefreshCwIcon />
      </IconButton>
    </div>
  )
}
```

## Accessibility

**Semantics.** A native `button`, or the element passed with `asChild`.

**Labels.** `label` is required and becomes `aria-label`. The tooltip repeats it on screen only: it is not added as a description, so a screen reader does not read the label twice.

**Focus.** It is in the tab order unless disabled; the focus outline shows on keyboard focus. With `tooltip`, focus opens the tooltip at once.

**Known limits.**

- A disabled icon button cannot be focused or hovered, so its tooltip cannot open. Say near it why it is disabled.
- A loading icon button is `aria-disabled`, not `disabled`: it keeps its focus and tab stop, and refuses presses until the work ends.

### Keyboard

| Key | Behaviour |
| --- | --- |
| Enter | Activates the button. |
| Space | Activates the button. |
| Escape | With `tooltip`, closes the tooltip. |

## API

### IconButton

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label` (required) | `string` |  | What the button does, in words: its accessible name, and the tooltip's text. Required. |
| `asChild` | `boolean` |  | Render the child element instead (a link), with the button's look and behaviour merged onto it. |
| `loading` | `boolean` |  | Shows the spinner in place of the leading icon, sets `aria-busy` and `aria-disabled` and ignores presses (a click, Enter, Space, a form's submission), while the button keeps its focus and its place in the tab order. With `asChild` the child (a link) gets the same. |
| `raised` | `boolean \| null` |  | Adds the raised shadow. |
| `rounded` | `boolean \| null` |  | A circle. |
| `severity` | `"success" \| "info" \| "warning" \| "help" \| "danger" \| "contrast" \| null` |  | The colour of the `default`, `outline`, `ghost` and `link` variants: `success`, `info`, `warning`, `help`, `danger` or `contrast`. |
| `size` | `"default" \| "xs" \| "sm" \| "lg"` |  | 24 px `xs`, 28 px `sm`, 36 px `default`, 42 px `lg`. The provider's `controlSize` when left out. |
| `tooltip` | `boolean` | `false` | Shows `label` in a tooltip on hover and keyboard focus. |
| `tooltipSide` | `"top" \| "bottom" \| "left" \| "right"` | `top` | The tooltip's side: `top` (default), `right`, `bottom` or `left`. |
| `variant` | `"link" \| "default" \| "destructive" \| "outline" \| "secondary" \| "ghost" \| null` | `ghost` | The emphasis, as Button's: `ghost` by default, or `default`, `outline`, `secondary`, `destructive`, `link`. |

**Also exported:** `IconButtonProps`, a TypeScript type; `IconButtonSize`, a TypeScript type.

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

**Data attributes:** `data-slot="icon-button"` (IconButton).

## Theming
