# Button

A button starts an action, such as saving a form, sending a message or opening a dialog.

A button starts an action when someone presses it: it saves, sends, deletes or opens something. A button that stays on or off is a [Toggle](/docs/toggle). A link that takes someone to another page is a link, even when it looks like a button.


## Usage

```tsx
import { Button, Flex } from '@kushagradhawan/kookie-ui';

export function SaveBar({ saving, onCancel, onSave }: { saving: boolean; onCancel: () => void; onSave: () => void }) {
  return (
    <Flex gap="2" justify="end">
      <Button emphasis="quiet" color="gray" onClick={onCancel}>
        Cancel
      </Button>
      <Button emphasis="loud" loading={saving} onClick={onSave}>
        Save changes
      </Button>
    </Flex>
  );
}
```

## Best practices

**Give each screen one loud button.** Set `emphasis="loud"` on the action people are most likely to take, and let the others stay medium or quiet. Two loud buttons side by side give people no answer to "which one do I press?"

**Rank buttons with emphasis, not with size.** Buttons in one row share one size, and `emphasis` tells them apart. A larger button reads as a different kind of control, not as the more important choice.

**Start the label with a verb.** "Save changes", "Send invite" and "Delete project" tell people what happens. "OK", "Yes" and "Submit" make them read the rest of the screen to find out.

**Don't make a destructive action the loud one.** Use `color="red"` for a button that deletes something, and confirm it in an [Alert Dialog](/docs/alert-dialog) when it cannot be undone. A loud red button invites the press it should slow down.

**Prefer a message to a disabled button.** A disabled button doesn't say why it is disabled. Where you can, keep it enabled and explain what is missing when someone presses it.

## Emphasis

`emphasis` sets how much attention a button asks for. Each value maps to one look, so you choose the rank and the theme chooses the style.

| Emphasis | Look | Use it for |
| --- | --- | --- |
| `loud` | `solid` | The main action on the screen. This is the default |
| `medium` | `raised` | Most other buttons |
| `quiet` | `ghost` | Cancel, close and actions in a toolbar |

`variant` sets the look directly, when you need `soft`, `surface` or `outline`, which have no rank of their own. Pass `emphasis` or `variant`, not both. The types reject the pair. If both arrive anyway, the button logs an error and `emphasis` wins.

## Size

`size` picks one of four heights: 28, 32, 40 and 48 pixels. Size 2 is the default and suits most screens. Use size 1 in dense toolbars and size 3 or 4 where a touch target needs room. A button is as tall as a [TextField](/docs/text-field) or a [Select](/docs/select) of the same size, so a row of controls lines up. `size` accepts a responsive object, such as `{ initial: '3', md: '2' }`.

## Examples

### Icon and label

Put the icon before the label, inside the button. The button sizes the icon and sets the gap, so don't size it yourself.

```tsx
<Button>
  <HugeiconsIcon icon={Add01Icon} strokeWidth={1.75} />
  New project
</Button>
```

### Icon only

`iconOnly` makes the button square. It has no visible text, so the types require `aria-label` or `aria-labelledby`. Add `tooltip` so people who use a pointer can see the name too.

```tsx
<Button iconOnly emphasis="quiet" aria-label="Search" tooltip="Search">
  <HugeiconsIcon icon={Search01Icon} strokeWidth={1.75} />
</Button>
```

### Waiting for a result

`loading` replaces the content with a spinner and blocks presses until it clears, so a slow request can't be sent twice. The button keeps its width and its focus, so the layout doesn't jump and keyboard users stay in place. Pass a boolean every time, such as `loading={saving}`, so the content stays mounted when loading starts and stops.

### Confirming a copy

`done` shows a tick in place of the leading icon. A button with no icon adds the tick before the label, so it grows by the tick's width. The icon comes back when `done` is false. It doesn't block presses. Screen readers don't announce the tick, so change the label too.

```tsx
const [copied, setCopied] = React.useState(false);

function copy() {
  navigator.clipboard.writeText(url);
  setCopied(true);
  setTimeout(() => setCopied(false), 1500);
}

<Button iconOnly emphasis="quiet" done={copied} aria-label={copied ? 'Copied' : 'Copy link'} onClick={copy}>
  <HugeiconsIcon icon={Copy01Icon} strokeWidth={1.75} />
</Button>
```

### Navigation

When a button-shaped control goes to another page, render your router's link with `asChild`. The element stays an anchor, so it opens in a new tab and shows its address like any link.

```tsx
<Button asChild>
  <Link href="/settings">Open settings</Link>
</Button>
```

### Full width

`fullWidth` stretches the button across its container. Use it for the single action at the end of a narrow form or a mobile sheet. It isn't available with `iconOnly`.

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `emphasis` | `'loud' \| 'medium' \| 'quiet'` | `'loud'` | How much attention the button asks for |
| `variant` | `'raised' \| 'solid' \| 'soft' \| 'surface' \| 'outline' \| 'ghost'` | `'solid'` | The look, set directly. Not with `emphasis` |
| `size` | `'1' \| '2' \| '3' \| '4'`, responsive | `'2'` | The height step |
| `color` | `AccentColor` | Theme accent | The colour. Gray fills with step 12 |
| `radius` | `'none' \| 'small' \| 'medium' \| 'large' \| 'full'` | Theme radius | The corner |
| `loading` | `boolean` | `false` | Shows a spinner and blocks presses |
| `done` | `boolean` | `false` | Shows a tick in place of the icon |
| `disabled` | `boolean` | `false` | Blocks presses and shows the disabled colours |
| `iconOnly` | `boolean` | `false` | Square, icon only. Requires `aria-label` or `aria-labelledby` |
| `fullWidth` | `boolean` | `false` | Fills the container's width. Not with `iconOnly` |
| `tooltip` | `ReactNode` | | Shows on hover and on keyboard focus |
| `tooltipSide` | `'top' \| 'right' \| 'bottom' \| 'left'` | `'top'` | Where the tooltip opens |
| `tooltipAlign` | `'start' \| 'center' \| 'end'` | `'center'` | How the tooltip lines up with the button |
| `asChild` | `boolean` | `false` | Renders the child element, such as a link, with the button's look |

Button also accepts every attribute of the HTML `button` element.

## Accessibility

| Key | Action |
| --- | --- |
| Enter or Space | Presses the button |
| Tab | Moves focus to the next control |

- **Name:** A screen reader announces the label. An icon-only button announces its `aria-label`, which the types require.
- **Loading:** The button sets `aria-busy` and announces that it is loading. It stays in the tab order, so focus doesn't jump to the top of the page.
- **Done:** The tick is hidden from screen readers. Change the label or `aria-label`, such as "Copy" to "Copied", so the change is heard.
- **Disabled:** A disabled `button` element leaves the tab order. A disabled link or `asChild` element sets `aria-disabled` and ignores presses, including the child's own `onClick`.
- **Tooltip:** The tooltip adds to `aria-describedby`. A description you pass yourself is kept.
- **Focus:** The focus ring shows for the keyboard only, and it uses the button's colour.
