<!-- Protocore Design System — Button -->
# Button

- **Category:** Inputs (`inputs`)
- **Slug:** `inputs/button`
- **Status:** stable
- **Platforms:** web, mobile
- **Import:** `import { Button } from "@protocore/pds";`
- **Docs:** https://pds.protocore.io/components/inputs/button

> The system action control — mono uppercase label, sharp corners, and a primary fill that inverts on hover.

## When to use

Reach for **Button** when activating it *does* something in place — submit a form, deploy a service, open a dialog, run a job.

- Navigating to another route or URL? Use **Link** (even when it looks like a button) so it renders a real `<a>` with correct semantics, or pass `asChild` and wrap an anchor.
- A compact, icon-only action in a toolbar? Use **IconButton**, which requires an `aria-label`.
- Holding a *selected* state that filters or toggles a view? Use **Chip** — it is a pressed toggle, not a one-shot action.

## Mobile (React Native)

**Preview.** `@protocore/pds-mobile` ships the React Native sibling of **Button**. It mirrors the web API where React Native allows; the package is a **preview** with no device-level QA yet, so pin it and expect small changes.

Import it from the mobile package (not `@protocore/pds`), inside a `<PdsProvider>` — there is no stylesheet, so `style` (a `ViewStyle`) replaces `className` and every value comes from the theme:

```tsx
import { Button } from "@protocore/pds-mobile";
```

**Parity with web.** It renders a `Pressable`, not an HTML `<button>`, and has no `asChild` — React Native has no polymorphic `Slot`.

- There is no `:hover` on touch: `primary` inverts (and `danger` fills) on **press** via `Pressable`'s `pressed` state, not hover.
- Use `onPress` instead of `onClick`; for navigation, wrap your router's pressable or pass an `onPress` handler.
- a11y is built in: `accessibilityRole="button"` plus `accessibilityState` `{ disabled, busy }` (set by `loading`).

```tsx
<Button variant="primary" size="md" onPress={() => deploy()}>
  Deploy service
</Button>
```

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `asChild` | `boolean` | no | `false` | Render as the single child element (Radix `Slot`) instead of a `<button>` — e.g. wrap a link. Icon slots are not injected in this mode. |
| `className` | `string` | no | — | — |
| `endIcon` | `ReactNode` | no | — | Trailing icon slot (any `ReactNode`). Ignored when `asChild`. |
| `full` | `boolean` | no | `false` | Stretch to fill the width of the container (block button). |
| `loading` | `boolean` | no | `false` | Show an inline spinner, set `aria-busy`, and disable the control while a background action runs. |
| `size` | `enum` | no | `md` | Control height + horizontal padding: sm 32 · md 36 · lg 40 · xl 48. |
| `startIcon` | `ReactNode` | no | — | Leading icon slot (any `ReactNode`). Suppressed while `loading` and ignored when `asChild`. |
| `style` | `CSSProperties` | no | — | — |
| `variant` | `enum` | no | `primary` | Visual weight. `primary` inverts on hover; `secondary`/`ghost` are quieter; `danger` is a destructive outline that fills on hover. |

## Examples

### Basics

A `Button` triggers an action in place. It renders a native `<button type="button">` — attach your own `onClick`, or set `type="submit"` inside a form.

```tsx
import { Button } from "@protocore/pds";

export default function Basics() {
  return <Button>Deploy service</Button>;
}
```

### Variants

Four visual weights. `primary` inverts to an outline on hover; `secondary` and `ghost` are progressively quieter; `danger` is a destructive outline that fills on hover. Use exactly one `primary` per view.

```tsx
import { Button } from "@protocore/pds";

export default function Variants() {
  return (
    <div style={{ display: "flex", gap: 12, flexWrap: "wrap" }}>
      <Button variant="primary">Deploy</Button>
      <Button variant="secondary">Preview diff</Button>
      <Button variant="ghost">Cancel</Button>
      <Button variant="danger">Decommission</Button>
    </div>
  );
}
```

## Do & don't

**Do**

- Lead each view with a single primary Button and make everything else secondary or ghost.
- Reserve variant="danger" for destructive actions, and pair it with a confirmation step.
- Write labels as short verb phrases — "Deploy service", "Export ledger".
- Use asChild to render a real <a> whenever the control navigates.

**Don't**

- Don't stack multiple primary buttons competing for the same attention.
- Don't use a Button for pure navigation — an anchor styled as a button breaks middle-click and right-click.
- Don't disable the submit button as your only validation feedback; say what's missing.
- Don't pad labels with prose — one or two words, no trailing punctuation.

## Accessibility

**Keyboard**

| Keys | Action |
| --- | --- |
| `Tab / Shift+Tab` | Move focus onto / off the button. |
| `Space or Enter` | Activate the button. |

**Notes**

- Renders a native `<button type="button">`; set `type="submit"` when it submits a form.
- `loading` sets `aria-busy` and disables the control; the spinner and icon slots are `aria-hidden`.
- Disabled buttons leave the tab order — don't rely on disabling alone to explain why an action is unavailable.
- Icon-only actions must carry a visible label or an `aria-label`; use IconButton for that case.

## Related

`icon-button`, `button-group`, `chip`, `link`

---

© Protocore. All rights reserved. Use of the Protocore Design System requires prior written authorization from Protocore (contact@protocore.io). These machine-readable materials must not be ingested into ML-training datasets or derivative design systems. See https://pds.protocore.io/legal/
