<!-- Generated by scripts/export-manifest.ts from the component’s guidance, accessibility, anatomy, stories and props. Do not edit: run `npm run manifest`. -->

Markdown version of https://demogar.github.io/molaui/?path=/docs/components-actions-button--overview. Every docs link `?path=/docs/<id>` on this site has a Markdown version at `https://demogar.github.io/molaui/md/<id>.md`.

# Button

A cut-shape button for an action on this page, with IconButton for an action shown as an icon alone.

Status: Stable. Semantic versioning applies: nothing breaks without a deprecation first.

A cut shape. Hover reveals the band of the layer beneath — the edge grows outward as a `box-shadow`, so nothing around it moves. **Primary is ink, not red**: in a tool with a primary action in every panel, red has to keep meaning *this destroys something*.

## Usage

### When to use

- Starting an action: run, save, delete, cancel.
- Primary for the main action of a panel, secondary for the rest, danger only for destruction, ghost for the least emphasis.
- Work in flight after a press, with loading, which keeps the label and adds the working strip instead of a spinner.
- Icon-only actions in dense rows and toolbars, with IconButton and its required label.

### When not to

- Going to another page or URL. Use a link: an a element, or your router’s link styled with buttonVariants.
- A row of related actions above a table or canvas, with one tab stop. Use [Toolbar](https://demogar.github.io/molaui/md/components-layout-toolbar--overview.md).
- More actions on one object than deserve a button each. Use [Menu](https://demogar.github.io/molaui/md/components-overlays-menu--overview.md).
- Choosing between views, where the pressed state stays. Use [Segmented control](https://demogar.github.io/molaui/md/components-forms-segmented-control--overview.md).

### Do and don’t

- Do: Keep one primary button per panel, and keep danger for actions that destroy something.
- Don’t: Use danger red for emphasis, or set several primary buttons side by side.
- Do: Let a disabled button keep its stated look, and say nearby why it is unavailable.
- Don’t: Fade a button with opacity, or disable it with no explanation.

### Content

- Start with a verb and name the object: “Delete run”, “Start run”, not “OK”, “Yes” or “Submit”.
- Write labels in sentence case with no trailing period; the button sets them in capitals itself.
- Give IconButton a label that names the action and the object, “Copy run id”; it is also the tooltip.

## Examples

Each story is a live example in Storybook.

- [Disabled](https://demogar.github.io/molaui/?path=/story/components-actions-button--disabled)
- [IconButtons](https://demogar.github.io/molaui/?path=/story/components-actions-button--icon-buttons)
- [Loading](https://demogar.github.io/molaui/?path=/story/components-actions-button--loading)
- [OnInkPanel](https://demogar.github.io/molaui/?path=/story/components-actions-button--on-ink-panel)
- [Primary](https://demogar.github.io/molaui/?path=/story/components-actions-button--primary)
- [Sizes](https://demogar.github.io/molaui/?path=/story/components-actions-button--sizes)
- [Variants](https://demogar.github.io/molaui/?path=/story/components-actions-button--variants)
- [WithIcons](https://demogar.github.io/molaui/?path=/story/components-actions-button--with-icons)

## API

### `Button` (component)

- `variant`: `primary`, `secondary`, `danger`, `ghost` (default `primary`)
- `size`: `sm`, `md`, `lg` (default `md`)

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` |  | Classes for the button, merged with `cn()` so yours win a conflict. A string, not Base UI's function of state: style a state with its `data-*` attribute (`data-disabled:`). |
| `icon` | `ReactNode` |  | Leading icon. Decorative: the label is the accessible name. |
| `loading` | `boolean` | `false` | Work is in flight. The label stays (so the button never changes width mid-click), a strip of working relleno runs along its foot, and the button stops accepting presses while staying focusable and announced as busy. |
| `size` | `"sm" \| "md" \| "lg"` |  | The control height. Density already re-scales every size, so reach for a different one only to set a button apart from its neighbours, not to make a whole screen compact. |
| `variant` | `"primary" \| "secondary" \| "danger" \| "ghost"` |  | The weight of the action: `primary` (ink) for the panel's main action, `secondary` beside it, `danger` only for what destroys something, `ghost` for low-stakes actions in dense rows. |
| `withArrow` | `boolean` | `false` | Trailing arrow that slides 3px on hover — for actions that move you somewhere. |

Also accepts the props of `ButtonProps`, `NativeButtonProps`.

`className`, `ref`, `id` and the other DOM props go to `<button data-slot="button">`.

### `ButtonArrow` (component)

`className`, `ref`, `id` and the other DOM props go to `<svg data-slot="button-arrow">`.

### `IconButton` (component)

A square button that carries only an icon. `label` is required and becomes the accessible name and the native tooltip, because an icon-only control with no name is the single most common accessibility failure in tool UIs.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label` (required) | `string` |  | The accessible name. Required: an icon is not a label. |
| `className` | `string` |  | Classes for the button, merged with `cn()` so yours win a conflict. A string, not Base UI's function of state: style a state with its `data-*` attribute (`data-disabled:`). |
| `loading` | `boolean` |  | Work is in flight. The label stays (so the button never changes width mid-click), a strip of working relleno runs along its foot, and the button stops accepting presses while staying focusable and announced as busy. |
| `size` | `"sm" \| "md" \| "lg"` |  | The control height. Density already re-scales every size, so reach for a different one only to set a button apart from its neighbours, not to make a whole screen compact. |
| `variant` | `"secondary" \| "danger" \| "primary" \| "ghost"` |  | The weight of the action: `primary` (ink) for the panel's main action, `secondary` beside it, `danger` only for what destroys something, `ghost` for low-stakes actions in dense rows. |

Also accepts the props of `ButtonProps`, `NativeButtonProps`.

`className`, `ref`, `id` and the other DOM props go to `<button data-slot="button">`.

### `buttonVariants` (function)

A button is a cut shape, so its edge is a `box-shadow` keyline rather than a `border`. The signature hover reveals a band of the layer beneath the shape, which means the edge has to grow by several pixels without moving anything around it. A border would reflow the toolbar; a shadow does not.

Types: `ButtonArrowProps`, `ButtonProps`, `IconButtonProps`

## Accessibility

### Keyboard

| Key | What happens |
| --- | --- |
| Tab | Moves focus to the button, including while it is loading. |
| Enter or Space | Activates it, unless it is disabled or loading. |

### Mola handles

- Renders a native `<button>` through Base UI, so it is announced as a button with its label as the name.
- While `loading`, sets `aria-busy`, stops accepting presses and stays focusable, so focus is not thrown back to the top of the page.
- Hides the leading icon and the arrow from assistive tech, so the label alone is the name.
- Gives `IconButton` its `label` as both the `aria-label` and the native tooltip.

### You handle

- Write a label that names the action with a verb: Deploy, Save changes.
- Give every `IconButton` a `label`; the icon is not a name.
- Use a link styled with `buttonVariants` when the control goes somewhere rather than does something.

### Testing status

- Automated, jsdom: each of its 8 stories is rendered and checked with axe on every pull request.
- Automated, browser: every story is checked in Chromium with axe, colour contrast included, in both themes, and for overflow at 390px.
- Keyboard in unit tests: yes, every key in the table is pressed.
- Screen readers: not yet tested by hand.

## Related

### Use instead, when it fits better

- [Toolbar](https://demogar.github.io/molaui/md/components-layout-toolbar--overview.md)
- [Menu](https://demogar.github.io/molaui/md/components-overlays-menu--overview.md)
- [Segmented control](https://demogar.github.io/molaui/md/components-forms-segmented-control--overview.md)

### Pages that send readers here

- [Skeleton](https://demogar.github.io/molaui/md/components-feedback-skeleton--overview.md)

### Used in

- [Agent console](https://demogar.github.io/molaui/md/ai-agent-console--overview.md) (showcase)
- [Form example](https://demogar.github.io/molaui/md/components-forms-form-example--overview.md) (showcase)
- [Agent hand-off](https://demogar.github.io/molaui/md/patterns-agent-hand-off--overview.md) (pattern)
- [Confirmation](https://demogar.github.io/molaui/md/patterns-confirmation--overview.md) (pattern)
- [Empty states](https://demogar.github.io/molaui/md/patterns-empty-states--overview.md) (pattern)
- [Filtered table](https://demogar.github.io/molaui/md/patterns-filtered-table--overview.md) (pattern)
- [List and detail](https://demogar.github.io/molaui/md/patterns-list-and-detail--overview.md) (pattern)
- [Settings page](https://demogar.github.io/molaui/md/patterns-settings-page--overview.md) (pattern)
