Browse all of Kata

Button

Source docs/design/components/button.mdMarkdown

On this page

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

Light

Primary · full width by default

Record promotion

Primary · hugging, trailing glyph

Check in

Secondary

Add a note

Link

See all classes

Destructive · the secondary style, an ink outline

Delete event

Dark

Primary · full width by default

Record promotion

Primary · hugging, trailing glyph

Check in

Secondary

Add a note

Link

See all classes

Destructive · the secondary style, an ink outline

Delete event

Part of the Konjo design language. The rules for when each variant is right live in buttons; this page is the component. Source: 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 confirmation.

Not for navigation inside a list — that is a Row. Not for picking a value — that is a chip, a segmented control or a select trigger.

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

Light

Default

Save

Pressed · opacity 0.85

Save

Focus · 2px ring

Save

Loading

Submitting…

Secondary · focus

Add a note

Secondary · loading

Submitting…

Dark

Default

Save

Pressed · opacity 0.85

Save

Focus · 2px ring

Save

Loading

Submitting…

Secondary · focus

Add a note

Secondary · loading

Submitting…
  • 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.
  • 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 browser's. Button does not draw its own focus ring; on RN-web it relies on the browser's default focus outline. Input, SearchField and SelectTrigger draw the 2px border.focus themselves; the specimen shows the ring this component owes.

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, 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

Delete event

Do

An ink outline and a plain verb, behind a confirmation that names what goes.

Delete event

Don’t

A red fill. Red means “tap here”, so a red Delete reads as the thing to press.

Conferred by
Choose who conferred it

Choose who conferred it.

Record promotion

Do

Submit stays live. The tap validates and lands on the first problem.

Conferred by
Choose who conferred it
Record promotion

Don’t

A greyed submit. It is illegible or lying, and it never says what is missing.

Book a private lesson

Do

A 48pt primary carries rowTitle 16/600, in sentence case.

Book now

Don’t

A 48pt button carrying 13pt capitals: target size and label size decided apart.

Code

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.