# Button

MD3 buttons, the Expressive way.

## Quick reference

- `tone`: `primary` (default), `secondary`, `tertiary`, `destructive`, `success`, `warning`, `info`
- `variant`: `filled` (default), `tonal`, `elevated`, `outlined`, `text`, `link`
- `size`: `xs`, `sm` (default), `md`, `lg`, `xl`
- `shape`: `round` (default), `square`

## Use it for

- The main action on a screen or dialog. Use `filled`, once per view.
- The actions next to it. `tonal` for the second most important, `outlined`
  or `text` for the rest.
- An action that takes a moment. Pass `loading`; the label stays and a
  spinner appears.

## Not for

- A link that only changes the URL → give it `href` and it renders an `<a>`.
  `variant="link"` is for a link inside a sentence.
- An icon with no label → [Icon Button](/docs/components/icon-button).
- The floating main action on a phone → [FAB](/docs/components/fab).
- One action plus a menu of alternatives → [Split Button](/docs/components/split-button).
- Picking between options → [Segmented Button](/docs/components/segmented-button)
  or filter [Chips](/docs/components/chip).

## Do

- Keep one `filled` button per view. Everything else is `tonal`, `outlined`
  or `text`.
- Write labels in sentence case, verb first: "Save changes", not "SAVE" or
  "OK".
- Give destructive actions `tone="destructive"` and confirm them in a
  [Dialog](/docs/components/dialog).

## Don't

- Wrap a label onto two lines, or put two icons in one button.
- Disable a button to explain why it cannot be pressed. Keep it enabled and
  say why in the supporting text or a toast.
- Use `size="lg"` or `xl` in a dashboard. They are for landing screens.

## Pairs with

- [Icon Button](/docs/components/icon-button), [Button Group](/docs/components/button-group),
  [Split Button](/docs/components/split-button), [Dialog](/docs/components/dialog),
  [Toast](/docs/components/toast)

## Examples

### Variants

The five MD3 fill styles, in the spec's own order of emphasis, plus `link`.

```svelte
<script lang="ts">
	import { Button } from 'omaris';
</script>

<Button variant="elevated">Elevated</Button>
<Button variant="filled">Filled</Button>
<Button variant="tonal">Tonal</Button>
<Button variant="outlined">Outlined</Button>
<Button variant="text">Text</Button>
<Button variant="link">Link</Button>
```

### Tones

`tone` is the colour role. Every tone works with every fill style.

```svelte
<script lang="ts">
	import { Button } from 'omaris';
</script>

<div class="flex flex-col gap-3">
	<div class="flex flex-wrap items-center gap-2">
		<Button variant="filled" tone="primary" size="xs">primary</Button>
		<Button variant="filled" tone="secondary" size="xs">secondary</Button>
		<Button variant="filled" tone="tertiary" size="xs">tertiary</Button>
		<Button variant="filled" tone="destructive" size="xs">destructive</Button>
		<Button variant="filled" tone="success" size="xs">success</Button>
		<Button variant="filled" tone="warning" size="xs">warning</Button>
		<Button variant="filled" tone="info" size="xs">info</Button>
	</div>
	<div class="flex flex-wrap items-center gap-2">
		<Button variant="tonal" tone="primary" size="xs">primary</Button>
		<Button variant="tonal" tone="secondary" size="xs">secondary</Button>
		<Button variant="tonal" tone="tertiary" size="xs">tertiary</Button>
		<Button variant="tonal" tone="destructive" size="xs">destructive</Button>
		<Button variant="tonal" tone="success" size="xs">success</Button>
		<Button variant="tonal" tone="warning" size="xs">warning</Button>
		<Button variant="tonal" tone="info" size="xs">info</Button>
	</div>
	<div class="flex flex-wrap items-center gap-2">
		<Button variant="outlined" tone="primary" size="xs">primary</Button>
		<Button variant="outlined" tone="secondary" size="xs">secondary</Button>
		<Button variant="outlined" tone="tertiary" size="xs">tertiary</Button>
		<Button variant="outlined" tone="destructive" size="xs">destructive</Button>
		<Button variant="outlined" tone="success" size="xs">success</Button>
		<Button variant="outlined" tone="warning" size="xs">warning</Button>
		<Button variant="outlined" tone="info" size="xs">info</Button>
	</div>
</div>
```

### Sizes

MD3 Expressive's five heights: 32 / 40 / 56 / 96 / 136dp.

```svelte
<script lang="ts">
	import { Button } from 'omaris';
</script>

<Button size="xs">Extra small</Button>
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
```

### With icons

Hand it an `<svg>`; the button sets the size and the colour.

```svelte
<script lang="ts">
	import { Button, IconButton } from 'omaris';
</script>

{#snippet plus()}
	...
{/snippet}

<Button>{@render plus()}New invoice</Button>
<Button variant="tonal" size="md">{@render plus()}Bigger</Button>
<IconButton aria-label="New invoice">{@render plus()}</IconButton>
```

### Loading

The spinner grows in from zero width with a negative margin that cancels the gap, so an idle button measures as if it had no spinner and nothing jumps when one appears.

```svelte
<script lang="ts">
	import { Button } from 'omaris';

	let busy = $state(false);

	function save() {
		busy = true;
		setTimeout(() => (busy = false), 2000);
	}
</script>

<Button loading={busy} onclick={save}>Save changes</Button>
<Button variant="tonal" loading>Always loading</Button>
```

### Toggle

A toggle drops to a neutral surface when off and takes its tone when on.

```svelte
<script lang="ts">
	import { Button } from 'omaris';

	let starred = $state(false);
	let muted = $state(true);
</script>

<Button toggle bind:pressed={starred}>{starred ? 'Starred' : 'Star'}</Button>
<Button toggle bind:pressed={muted} variant="outlined" tone="tertiary">
	{muted ? 'Muted' : 'Mute'}
</Button>
```

### As a link

Give it `href` and it renders an `<a>`, keeping every anchor attribute.

```svelte
<script lang="ts">
	import { Button } from 'omaris';
</script>

<Button href="https://svelte.dev" target="_blank" rel="noreferrer">Open Svelte</Button>
<Button href="/docs/components/icon-button" variant="text">Icon Button</Button>
```

### Overridden

`class` is merged last, so a utility beats the component's own — including the height and the pill radius that `size` and `shape` set.

```svelte
<script lang="ts">
	import { Button } from 'omaris';
</script>

<Button class="h-12 rounded-none px-8">Squared and taller</Button>
<Button variant="tonal" class="w-full max-w-56 justify-between">
	Full width<span aria-hidden="true">→</span>
</Button>
```

## API

### Button

`import { Button } from 'omaris'`

MD3 buttons, the Expressive way.

Two axes instead of one long variant list: `variant` is the *fill style* (elevated / filled / tonal / outlined / text) and `tone` is the *color role* (primary / secondary / … / destructive), which resolves to the `--btn-*` custom properties in `tones.ts`. Every fill style works with every tone, with no combinatorial explosion of classes.

#### Props

| Prop | Type | Notes |
| --- | --- | --- |
| `variant?` | `ButtonVariant` | Fill style. MD3's five, plus `link` for inline navigation. One of `filled`, `tonal`, `elevated`, `outlined`, `text`, `link`. Default `'filled'`. |
| `tone?` | `ButtonTone` | Color role the fill style paints with. One of `primary`, `secondary`, `tertiary`, `destructive`, `success`, `warning`, `info`. Default `'primary'`. |
| `size?` | `ButtonSize` | MD3 Expressive heights: 32 / 40 / 56 / 96 / 136dp. One of `xs`, `sm`, `md`, `lg`, `xl`. Default `'sm'`. |
| `shape?` | `ButtonShape` | Pill (`round`) or MD3 rounded rectangle (`square`). One of `round`, `square`. Default `'round'`. |
| `icon?` | `boolean` | Square, label-less button. Requires an `aria-label`. Default `false`. |
| `ripple?` | `boolean` | Material ripple on press. Set false for a flat, instant button. Default `true`. |
| `confetti?` | `boolean \| ConfettiOptions` | Confetti on press. `true` for the default burst, or any `ConfettiOptions` — `confetti={{ preset: 'fireworks' }}`. Default `false`. |
| `loading?` | `boolean` | Grows a spinner in at the start of the button and blocks interaction. The label stays put; only an `icon` button swaps its icon out. Default `false`. |
| `toggle?` | `boolean` | Turns the button into a two-state toggle driven by `pressed`. Ignored when `href` is set — a link has no pressed state. Default `false`. |
| `pressed?` | `boolean` | Selected state of a toggle button. Bindable. Default `false`. |
| `class?` | `string` |  |
| `children?` | `Snippet` |  |
