# Button

> Triggers an action. Renders a <button>, or an <a> when href is set. Hue, fill and height are three separate props.

URL: https://apogee.avaui.dev/docs/button
Last updated: 2026-10-15

Triggers an action. Renders a <button>, or an <a> when href is set. Hue, fill and height are three separate props.

Last updated October 15, 2026

```astro
<Button>Get started</Button>
<Button variant="outline" color="neutral">Learn more</Button>
```

## Add

```sh
npx @ava-ui/apogee add button
```

Pulls in `Spinner` (for the loading state). Zero JS.

## Usage

```astro
---
import Button from '@/components/apogee/Button/Button.astro'
---
<Button>Save</Button>
<Button variant="outline" color="neutral">Cancel</Button>
<Button color="critical">Delete</Button>
<Button href="/pricing">See pricing</Button>
```

## API

`@composable` — exposed to Content Editors in AVA. Any other attribute passes through to the root `<button>` or `<a>`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `color` | `'primary' \| 'neutral' \| 'critical' \| 'positive' \| 'warning'` | `'primary'` | The hue. |
| `variant` | `'solid' \| 'outline' \| 'ghost' \| 'faded' \| 'link'` | `'solid'` | The fill style; `link` is text-sized with no box. |
| `size` | `'small' \| 'medium' \| 'large' \| 'xlarge'` | `'medium'` | Height: 28 / 36 / 48 / 56px. |
| `rounded` | `boolean` | `false` | Fully round ends. |
| `raised` | `boolean` | `false` | Adds the raised shadow. |
| `fullWidth` | `boolean` | `false` | Stretches to the container width. |
| `iconOnly` | `boolean` | `false` | A square button for a single icon. Give it an `aria-label`. |
| `href` | `string` | — | Renders an `<a>` instead of a `<button>`. |
| `type` | `'button' \| 'submit' \| 'reset'` | `'button'` | Native button type (ignored with `href`). |
| `loading` | `boolean` | `false` | Shows a centred spinner, hides the content and blocks interaction; the width does not change. |
| `showTextWhileLoading` | `boolean` | `false` | Keeps the label visible next to the spinner while loading. |
| `disabled` | `boolean` | `false` | Disables the control (`aria-disabled` on links). |
| `highlighted` | `boolean` | `false` | Keeps the hover fill on: an active filter, an open menu’s trigger. |
| `command` | `'show-modal' \| 'close' \| 'request-close' \| 'show-popover' \| 'hide-popover' \| 'toggle-popover'` | — | Invoker command sent to the `commandfor` target; works with no JS. |
| `commandfor` | `string` | — | `id` of the dialog or popover the command targets. |
| `class` | `string` | — | Extra classes, appended last. |

Slots: `start` and `end` for icons; the default slot is the label.

For an inline text link in running text use `Link`; `variant="link"` is a button that only looks like one (a “Cancel” beside a solid “Save”).

The root carries `data-icon-only` and `data-loading` while those states apply, so you can style them from outside.

## Variants × colors

Every `variant` for every `color`:

```astro
<Button color="primary">Primary</Button>
<Button color="neutral">Neutral</Button>
<Button color="critical">Critical</Button>
<Button color="positive">Positive</Button>
<Button color="warning">Warning</Button>

<Button variant="faded" color="primary">Primary</Button>
<Button variant="faded" color="neutral">Neutral</Button>
<Button variant="faded" color="critical">Critical</Button>
<Button variant="faded" color="positive">Positive</Button>
<Button variant="faded" color="warning">Warning</Button>

<Button variant="outline" color="primary">Primary</Button>
<Button variant="outline" color="neutral">Neutral</Button>
<Button variant="outline" color="critical">Critical</Button>
<Button variant="outline" color="positive">Positive</Button>
<Button variant="outline" color="warning">Warning</Button>

<Button variant="ghost" color="primary">Primary</Button>
<Button variant="ghost" color="neutral">Neutral</Button>
<Button variant="ghost" color="critical">Critical</Button>
<Button variant="ghost" color="positive">Positive</Button>
<Button variant="ghost" color="warning">Warning</Button>
```

## Highlighted

Every variant with `highlighted`:

```astro
<Button variant="solid" highlighted>Solid</Button>
<Button variant="faded" highlighted>Faded</Button>
<Button variant="outline" highlighted>Outline</Button>
<Button variant="ghost" highlighted>Ghost</Button>
```

## Link

A button with the weight of a link: no box, underline on hover, every colour.

```astro
<Button variant="link">Cancel</Button>
<Button variant="link" color="neutral">Learn more</Button>
<Button variant="link" color="critical">Delete</Button>
<Button variant="link" color="positive">Approve</Button>
<Button variant="link" color="warning">Review</Button>
```

## Sizes

```astro
<Button size="small">Small</Button>
<Button size="medium">Medium</Button>
<Button size="large">Large</Button>
<Button size="xlarge">Extra large</Button>
<Button size="small" iconOnly aria-label="Search"><Search /></Button>
<Button iconOnly aria-label="Search"><Search /></Button>
<Button size="large" iconOnly aria-label="Search"><Search /></Button>
<Button size="xlarge" iconOnly aria-label="Search"><Search /></Button>
```

## Shape and width

```astro
<Button rounded>Rounded</Button>
<Button raised variant="outline" color="neutral">Raised</Button>
<Button rounded iconOnly aria-label="Add"><Plus /></Button>
<Button fullWidth>Full width</Button>
```

## States

```astro
<Button>Save changes</Button>
<Button loading>Save changes</Button>
<Button loading showTextWhileLoading>Save changes</Button>
<Button disabled>Disabled</Button>
<Button variant="outline" color="neutral" disabled>Disabled</Button>
<Button href="/docs" disabled>Disabled link</Button>
```

-   **Hover** — solid fills move to their `-highlighted` token; faded, outline and ghost fills get the hue’s `-faded-highlighted` tint.
-   **Active** — scales to 98.5% (not under `prefers-reduced-motion`).
-   **Focus** — a 2px outline in the focus token (`--outline-color-focus`, the primary border by default), offset 2px; `color="critical"` focuses in the critical colour.
-   **Loading** — `aria-busy="true"` and `data-loading`; a centred `Spinner` fades the content out (it stays in the box and in the accessibility tree, so the width and the name hold) and the control is disabled. `showTextWhileLoading` keeps the label beside the spinner.
-   **Disabled** — the disabled fill and text tokens; a disabled link drops its `href` and carries `aria-disabled="true"`.

## Examples

With icons in the `start` and `end` slots:

```astro
<Button><Mail slot="start" /> Email us</Button>
<Button variant="outline" color="neutral">Next <ArrowRight slot="end" /></Button>
```

As a link, and the text link that is not a Button:

```astro
<Button href="/docs">Read the docs</Button>
<Link href="/docs">Read the docs</Link>
```

## Notification count

A Badge over an icon button’s corner. Put the count in the label too, so it is announced.

```astro
<span class="relative inline-flex">
<Button variant="ghost" color="neutral" iconOnly aria-label="Notifications, 3 unread"><Bell /></Button>
<Badge rounded size="small" color="critical" variant="solid" class="pointer-events-none absolute -top-1 -end-1" aria-hidden="true">3</Badge>
</span>
```

## Accessibility

-   A real `<button>` (or `<a>` with `href`), so keyboard activation, form submission and screen-reader naming are native.
-   `iconOnly` buttons need an `aria-label`; the icon alone has no name.
-   Loading sets `aria-busy` and disables the control, so the state is announced and a double submit is impossible.
-   A disabled link keeps its text, loses its `href`, and reports `aria-disabled="true"`; it is not focusable, matching a disabled button.
-   The active-state scale honours `prefers-reduced-motion`.

## Tokens

| Token | Used for |
| --- | --- |
| `--background-{hue}` / `-highlighted` | Solid fill and its hover |
| `--background-{hue}-faded` / `-faded-highlighted` | Faded fill; hover of faded, outline and ghost |
| `--foreground-{hue}` | Text of faded, outline and ghost |
| `--on-{hue}` | Text on a solid fill |
| `--border-{hue}` | Outline border; `--border-primary` is also the focus ring |
| `--background-disabled` / `--foreground-disabled` / `--border-disabled` | Disabled state |
| `--shadow-outline` | The neutral outline variant |
| `--shadow-raised` | `raised` |
| `--radius-sm` / `--radius-md` | Corners for `small` / the rest |

## Class merging

There is none. `class` is appended after the component’s own utilities, so a utility you pass wins only when it is more specific or later in the generated stylesheet. To change padding or colour, use the props; to add layout (`mt-4`, `self-end`), pass a class.

## Do / Don’t

-   **Do** use one solid `primary` button per view for the main action; pair it with `outline` or `ghost` neutral buttons for the rest.
-   **Do** use `color="critical"` for destructive actions, and confirm them.
-   **Don’t** use a Button for navigation inside running text; use `Link`.
-   **Don’t** put more than one `fullWidth` button side by side.

## Content

-   Verb first, sentence case, no ending period: “Save changes”, “Send invite”.
-   Two or three words. The label says what happens, not “OK” or “Submit”.

## Related

[

ToggleButtonA two-state button (aria-pressed) — icon and/or text, on or off.

](https://apogee.avaui.dev/docs/toggle-button)[

ButtonGroupJoins adjacent Buttons into a single control, collapsing the inner corners and overlapping borders.

](https://apogee.avaui.dev/docs/button-group)[

ActionableThe bare interactive primitive. A button, link or focusable div with the focus ring and disabled handling, nothing else.

](https://apogee.avaui.dev/docs/actionable)[

LinkAn inline text link — for a link that should look like a button, use Button with href instead.

](https://apogee.avaui.dev/docs/link)
