# Button

A button that can render as something else, with disabled semantics and a loading state.

`Button` renders a `<button>`, or whatever element you point it at, and keeps
the disabled and loading semantics correct either way. Use it wherever you would
write `<button>`; if the button should stay pressed once activated, use
[`Toggle`](/components/toggle).

::component-preview{name="ButtonPreview"}
```vue
<template>
  <div class="flex flex-wrap items-center gap-3">
    <Button :class="style">Save</Button>
    <Button :class="style" loading>Saving…</Button>
    <Button :class="style" disabled>Disabled</Button>
  </div>
</template>

<script setup lang="ts">
import { Button } from '@maas/mirror/vue'

const style = [
  'inline-flex h-12 items-center justify-center gap-1.5 px-[1.125rem] whitespace-nowrap',
  'rounded-component-lg border-2 border-[transparent] type-component-lg',
  'bg-primary-solid text-primary-on-solid',
  'transition-all duration-100 ease-linear',
  'outline-4 outline-transparent focus-visible:focus-ring',
  'hover:bg-primary-solid-hover active:bg-primary-solid-active',
  'data-[loading=true]:bg-disabled-solid data-[loading=true]:text-disabled-on-solid',
  'data-[disabled=true]:bg-disabled-solid data-[disabled=true]:text-disabled-on-solid',
].join(' ')
</script>
```
::

## Usage guidelines

- A button whose content is a glyph has no accessible name, so give it an
  `aria-label`.
- The button works out whether it renders a real `<button>`, so an `<a>` or a
  `<div>` gets the role, the tab stop and the keyboard activation without being
  told. Give it a real element through `as`, or put one in the slot under
  `asChild`.

## Anatomy

Render it where you would write `<button>`.

::component-anatomy
---
parts:
  - name: Button
    required: true
    description: Renders a <button> unless told otherwise.
---
::

```vue
<script setup lang="ts">
import { Button } from '@maas/mirror/vue'
</script>

<template>
  <Button @click="save">Save</Button>
</template>
```

## Examples

### Rendering another element

`as` swaps the rendered element, while `asChild` merges everything onto the
single child you provide. The button works out on its own whether it is still a
real `<button>`: a string `as` names the element, and under `asChild` it
inspects the child. It cannot inspect a component child, so slot the `<button>`
element itself where the semantics matter. Getting that wrong logs
`unexpected_native_button` in development, and the semantics stay as they were
decided, because changing them after mount would break hydration.

```vue
<template>
  <Button as="a" href="/docs">Read the docs</Button>

  <Button as-child>
    <NuxtLink to="/docs">Read the docs</NuxtLink>
  </Button>
</template>
```

### Swapping content while loading

`loading` arrives as a slot prop as well as an attribute, so the markup can
branch on it without a second ref.

```vue
<template>
  <Button :loading="saving">
    <template #default="{ loading }">
      <app-spinner v-if="loading" />
      <span v-else>Save</span>
    </template>
  </Button>
</template>
```

### Keeping a disabled button focusable

A disabled button leaves the tab order, which is right for a control nobody
should reach. Where a tooltip has to explain why the button is off,
`focusableWhenDisabled` keeps it reachable and reports the state through
`aria-disabled` instead, so a tooltip or a screen reader still gets to it while
activation stays blocked.

```vue
<template>
  <Button disabled focusable-when-disabled>Publish</Button>
</template>
```

`loading` already does this on its own, since a button that is busy for a moment
should not move focus away mid-action.

### Choosing the activation keys

A non-native button answers Enter and Space. `keys` narrows that, and on a
native button an omitted Enter is blocked rather than ignored, since the browser
would otherwise synthesise a click of its own.

```vue
<template>
  <Button :keys="['Space']" as="div">Save</Button>
</template>
```

## API reference

**Standalone.** Ordinary props, no store. `as` and `asChild` come from every
Mirror part and are documented in [Composition](/components/composition).

### `Button`

Renders a `<button>`.

#### Props

::docs-table
---
columns:
  - label: Prop
  - label: Type
  - label: Default
rows:
  - items:
      - label: type
        description: Applied only when the rendered element is a `<button>`.
      - label: '''button'' | ''submit'' | ''reset'''
      - label: '''button'''
  - items:
      - label: disabled
        description: Blocks activation.
      - label: boolean
      - label: 'false'
  - items:
      - label: loading
        description: Marks the button busy. Blocks activation and sets `aria-busy`.
      - label: boolean
      - label: 'false'
  - items:
      - label: focusableWhenDisabled
        description: 'Keeps a disabled button [in the tab order](#keeping-a-disabled-button-focusable), under `aria-disabled`.'
      - label: boolean
      - label: 'false'
  - items:
      - label: keys
        description: Which keys activate the button.
      - label: 'Array<''Enter'' | ''Space''>'
        escape: true
      - label: '[''Enter'', ''Space'']'
---
::

#### Emits

None. Listeners pass through to the rendered element, so `@click` and `@focus`
behave as they would on a plain `<button>`.

#### Slot props

::docs-table
---
columns:
  - label: Prop
  - label: Type
rows:
  - items:
      - label: disabled
        description: Mirrors `data-disabled`.
      - label: boolean
  - items:
      - label: loading
        description: Mirrors `data-loading`.
      - label: boolean
  - items:
      - label: pressed
        description: Mirrors `data-pressed`.
      - label: boolean
---
::

#### Data attributes

::data-attributes
::

#### CSS variables

::css-variables
::

#### Errors

::docs-table
---
columns:
  - label: Code
rows:
  - items:
      - label: unexpected_native_button
        description: 'The element in the document is a `<button>` while the button decided before mount that it was not one. A development warning, not a throw.'
---
::

## Accessibility

A native `<button>` needs no help. A non-native one, which is anything `as` or
`asChild` renders that is not a `<button>`, gets `role="button"`,
`tabindex="0"` and both activation keys, and reports `aria-disabled` when it is
disabled. Disabled, it leaves the tab order like a real button would, unless
`focusableWhenDisabled` says otherwise.

::docs-table
---
columns:
  - label: Key
  - label: Behaviour
rows:
  - items:
      - label: Enter
      - label: Activates the button, on `keydown`.
        plaintext: true
  - items:
      - label: Space
      - label: Activates the button, on `keyup`. The `keydown` default is prevented.
        plaintext: true
---
::
