<!-- https://getkonjo.com/design/components/button · source: docs/design/components/button.md -->

# Button

The thing you tap to do something. Three variants — primary, secondary, link — and no disabled state.

```kata-specimen
button-variants
```

> Part of the [Konjo design language](../konjo-design-language.md). The rules for when each
> variant is right live in [buttons](../patterns/buttons.md); this page is the component.
> Source: [`Button.tsx`](../../../src/components/ui/Button.tsx).

## When to use it

- **Primary** for the one action the screen exists for: "Record promotion", "Check in". At most
  one per screen, and zero is normal on an index.
- **Secondary** for every other action that deserves a shape: "Add a note", "Try again" under a
  failed section.
- **Link** for a quiet way out or a way further: "See all classes", "Not now".
- **Destructive** is not a variant. It is a secondary with a plain verb ("Delete event"), behind
  the [serious-action](../patterns/destructive.md) confirmation.

Not for navigation inside a list — that is a [Row](row.md). Not for picking a value — that is
a [chip](tag-chip.md), a [segmented control](segmented-control.md) or a
[select trigger](select-trigger.md).

## Anatomy

| Part | Primary | Secondary | Link |
|---|---|---|---|
| Height floor | `controlHeight.primary` 48 | `controlHeight.control` 44 | `touchTarget` 44, plus `hitSlop` 12 left and right |
| Padding | `spacing.s12` × `spacing.s20` | `spacing.s12` × `spacing.s16` | `spacing.s12` vertical |
| Shape | `radius.pill` | `radius.pill` | — |
| Fill | `brand.red` | none | none |
| Edge | none | 1px `border.control` | none |
| Label | `typography.rowTitle` 16/600, `brand.onRed` | `typography.label` 13/700, `text.primary` | `typography.label` 13/700, `accent.red` |
| Glyph gap | `spacing.s6` | `spacing.s6` | `spacing.s6` |

Leading and trailing glyphs are `iconSize.inline` 16 in the label's colour. A primary is full
width by default; secondary and link hug their label.

## States

```kata-specimen
button-states
```

- **Default** as drawn above.
- **Pressed**: the whole button drops to `opacity.pressed` 0.85. Nothing else moves.
- **Focus**: a 2px `border.focus` ring outside the shape, on the page behind it. A hero CTA's
  ring flips to `border.focusOnHero` — see [Hero](hero.md).
- **Loading**: the fill stays, the label swaps to "Submitting…" (or your `loadingLabel`), a 16pt
  spinner takes the leading slot, taps are ignored and `accessibilityState={{ busy: true }}`
  is set. The button stays exactly as loud.
- **Disabled**: there is none, on purpose. There is no legal disabled text colour, so a greyed
  label is either unreadable or lying about being tappable. Validate on tap and jump to the
  first problem instead.
- **Error**: a failed submit relabels the primary "Try again" and puts a bordered block above it.
  The form stays.

**On web the ring is the component's own.** `Button` draws the 2px `border.focus` ring itself
(2pt outside the pill), as an outline so focusing it moves nothing. It shows for keyboard focus only — a
mouse or touch press does not light it — and native, which has no keyboard focus, is unchanged.

## Content

- Sentence case, a verb first: "Record promotion", not "Promotion" or "OK".
- Never type capitals — VoiceOver spells them. The only uppercase button is the CTA inside a
  [Hero](hero.md), and that is applied with `textTransform`.
- Name the thing: "Delete event", not "Delete". The label is the last thing read before a
  permanent change.
- Long labels wrap; the button grows. Never truncate.

## Accessibility

- Role `button`. The accessible name defaults to the visible label; pass `accessibilityLabel`
  only when the label alone is a fragment.
- Targets are 44pt or more: primary is 48, secondary 44, and a link gets `hitSlop` to make up
  the width its text does not have.
- `brand.onRed` on `brand.red` is 5.00:1 in both themes. `accent.red` on `surface.page` is
  5.25:1 in light; that is why a link is `accent.red` and never `brand.red`.
- Loading announces as busy rather than disabled, so a screen reader hears that the tap landed.

## Do and don't

```kata-specimen
button-destructive
```

```kata-specimen
button-disabled
```

```kata-specimen
button-label
```

## Code

```tsx
import { Button } from '../../components/ui';

<Button
  label="Record promotion"
  onPress={submit}
  loading={saving}
  testID="promotion-submit"
/>

<Button variant="secondary" label="Delete event" onPress={armDelete} testID="event-delete" />

<Button variant="link" label="See all classes" onPress={openClasses} testID="train-all-classes" />
```

`tone="red"` is deprecated and changes nothing: red is the default action colour.
