ButtonDemo,
  ButtonVariants,
  ButtonSizes,
  ButtonIcons,
  ButtonLoading,
  ButtonDisabled,
  ButtonFullWidth,
} from "@/examples/button";

# Button

Triggers an action — submitting a form, opening a dialog, saving a change. Use a link instead when the result is navigation.

```tsx
<Button variant="primary">Save changes</Button>
<Button>Cancel</Button>
```

## Import

```tsx
import { Button } from "@meridui/react";
```

## Examples

### Variants

`primary` is the single accent action on a screen. `secondary` (the default) is for everything else; `ghost` sits quietly in toolbars and dense rows; `danger` confirms destructive actions; `link` looks like inline text but keeps button semantics.

```tsx
<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="danger">Danger</Button>
<Button variant="link">Link</Button>
```

### Sizes

`sm` is 36px, `md` 44px (default) and `lg` 50px tall. Match the size of neighbouring inputs so a row of controls shares one baseline.

```tsx
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>
```

### With icons

`leadingIcon` and `trailingIcon` take any node. They are wrapped in `aria-hidden` spans, so the label alone names the button.

```tsx
<Button variant="primary" leadingIcon={<PlusIcon />}>New project</Button>
<Button trailingIcon={<ArrowRightIcon />}>Continue</Button>
<Button variant="danger" leadingIcon={<TrashIcon />}>Delete</Button>
```

### Loading

`loading` overlays a spinner, keeps the button's width so the layout does not jump, sets `aria-busy` and `aria-disabled`, and swallows clicks. The button stays focusable, so keyboard focus is not lost mid-request.

```tsx
const [saving, setSaving] = useState(false);

<Button
  variant="primary"
  loading={saving}
  onClick={() => {
    setSaving(true);
    window.setTimeout(() => setSaving(false), 1500);
  }}
>
  Save changes
</Button>
```

### Disabled

`disabled` is the native attribute: the button leaves the tab order and ignores clicks. Prefer explaining why an action is unavailable over silently disabling it.

```tsx
<Button variant="primary" disabled>Publish</Button>
<Button disabled>Export</Button>
```

### Full width

`fullWidth` stretches the button to its container — useful in narrow forms and on mobile.

```tsx
<Button variant="primary" fullWidth>Create account</Button>
```

### As a router link

Use `asChild` to render your router's link with button styling, so navigation stays a real link:

```tsx
import Link from "next/link";

<Button asChild variant="primary">
  <Link href="/signup">Start free</Link>
</Button>
```

## API reference

`Button` forwards its ref to the `<button>` and passes every other native button attribute through. `type` defaults to `"button"`, so a button inside a form does not submit it unless you set `type="submit"`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `"primary" \| "secondary" \| "ghost" \| "danger" \| "link"` | `"secondary"` | Visual style. primary is the single accent action. |
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Height and padding: sm 36px, md 44px, lg 50px. |
| `loading` | `boolean` | `false` | Shows a spinner, keeps the width, sets aria-busy and blocks clicks. |
| `leadingIcon` | `ReactNode` |  | Icon before the label. Hidden from assistive tech. |
| `trailingIcon` | `ReactNode` |  | Icon after the label. Hidden from assistive tech. |
| `fullWidth` | `boolean` | `false` | Stretches the button to the width of its container. |
| `asChild` | `boolean` | `false` | Render the single child element (e.g. a router `<Link>`) styled as this button instead of a `<button>`. Props, ref, handlers and className merge onto the child; its children are wrapped in the button's content span. `disabled`/`loading` become `aria-disabled` and block clicks. Works from server components. |
| `type` | `"button" \| "submit" \| "reset"` | `"button"` | Native button type. |
| `disabled` | `boolean` |  | Native disabled attribute. |

## Styling

| Hook | Values |
| --- | --- |
| `.mrd-button` | Root `<button>` |
| `.mrd-button__content` | Wrapper around icons and label (hidden while loading) |
| `.mrd-button__icon` | Leading / trailing icon slot |
| `.mrd-button__spinner` | Spinner shown while loading |
| `data-variant` | `primary` · `secondary` · `ghost` · `danger` · `link` |
| `data-size` | `sm` · `md` · `lg` |
| `data-loading` | Present while loading |
| `data-full-width` | Present when `fullWidth` |

Component variables, set per size: `--mrd-button-height`, `--mrd-button-padding`, `--mrd-button-font`.

```css
.toolbar .mrd-button {
  --mrd-button-height: 32px;
  --mrd-button-padding: 0 12px;
}
```

## Accessibility

- Renders a native `<button>`, so Enter and Space activate it and it takes part in forms.
- The visible label is the accessible name. For icon-only actions use [IconButton](https://meridui.dev/docs/components/icon-button), which requires a `label`.
- While `loading`, the button reports `aria-busy="true"` and `aria-disabled="true"` but keeps focus.

| Keys | Action |
| --- | --- |
| `Tab` | Moves focus to the button. |
| `Enter` | Activates the button. |
| `Space` | Activates the button. |

## Guidelines

- **Do:** Use one `primary` button per view, labelled with a verb that says what happens: “Save changes”, “Send invite”.
- **Don't:** Put two primary buttons side by side, or use vague labels like “OK” and “Submit”.

- **Do:** Use `loading` while a request is in flight so the button keeps its place and focus.
- **Don't:** Swap the label for a spinner by hand or disable the button during the request — focus is lost and the width jumps.

## Related

- [IconButton](https://meridui.dev/docs/components/icon-button) — square, icon-only actions.
- [Link](https://meridui.dev/docs/components/link) — navigation rather than actions.
- [Spinner](https://meridui.dev/docs/components/spinner) — the indicator used by `loading`.

---

Source: https://meridui.dev/docs/components/button
