Sakuga Back to Sakuga
Theme
Sound
Jump to a component

Core · Forms

Checkbox

Checkbox is one thing chosen or not, among others that may be chosen too: the files to queue, the rows to delete, the terms you agree to. Its box has an edge when off and fills with the accent and a check when on.

@anicanon/sakuga/uiv1.0Used onWeb · iOS · Android

Live, from @anicanon/sakuga 2.0.0

Sakuga for iOS and Android is coming

iOS

Coming soon

Checkbox for iOS is in the roughs. The tokens it will read are the ones on this page, the same on every platform.

Android

Coming soon

Checkbox for Android is in the roughs. The tokens it will read are the ones on this page, the same on every platform.

Use it for
Choosing any number of things at once, or for one agreement: rows for a bulk action, files to queue, "I agree to the terms".
Not for
  • A setting that takes effect the moment it changesSwitch
  • Choosing among options drawn as tilesOptionTiles with multiple
  • Choosing one thing among many, with pictures and searchPicker

Guidelines

  • checked and onChange: a press calls onChange with the new value. label always names it; showLabel sets the words beside the box, with an optional hint under them, and a press on the words flips it too.
  • mixed is for a box that chooses a set when only some of the set is chosen (select all, over a list where 3 of 12 are chosen). It fills with a dash. Pressing it chooses them all; pressing again chooses none.
  • The box is 20 px (a 16 px mark with 2 px around it) with a 2 px edge in the control border, corner radius-xs. On, it fills with the accent and the mark is on-accent. The hit area reaches size-target (44) around it.
  • Turning it on or off eases the fill on motion-fast; the mark settles in on motion-settle when it changes while you watch. A press sinks the box to scale-press-sm on motion-instant. Under reduced motion the mark just appears.
  • With sound on, checking or unchecking plays the on or off sound; mixed to on counts as on (README · Sound).
  • In a list, use ListRow's checked and onCheck: the row carries the box, named "Select" and the row's title.
  • What the choices do waits for an action: a bar with the count ("3 selected") and the action ("Queue 3 files").
CheckboxExample.jsxReact
import { useState } from 'react';
import { Checkbox } from '@anicanon/sakuga/ui';

export default function CheckboxExample() {
  const [value, setValue] = useState({ a: true, b: false, c: true });
  const v = value;
  const n = (v.a ? 1 : 0) + (v.b ? 1 : 0) + (v.c ? 1 : 0);
  const set = (k) => (x) => {
    const o = Object.assign({}, v);
    o[k] = x;
    setValue(o);
  };
  const all = (x) => setValue({ a: x, b: x, c: x });
  return (
    <>
      <Checkbox
        label="All files"
        showLabel
        checked={n === 3}
        mixed={n > 0 && n < 3}
        onChange={all}
      />
      <Checkbox
        label="rain-on-glass.wav"
        showLabel
        checked={v.a}
        onChange={set('a')}
      />
      <Checkbox
        label="toast-take-2.mp4"
        showLabel
        checked={v.b}
        onChange={set('b')}
      />
      <Checkbox
        label="cold-open-boards.pdf"
        showLabel
        checked={v.c}
        onChange={set('c')}
      />
      <Checkbox
        label="Keep the originals"
        hint="Locked while the files are queued"
        showLabel
        checked
        disabled
      />
    </>
  );
}

PropsCheckboxProps

labelrequired
string
What it chooses, read by screen readers.
checked
boolean
mixed
boolean
Over a set when only some of it is chosen: a dash, read "mixed". A press chooses all.
showLabel
boolean
Sets the label beside the box; a press on the words flips it too.
hint
string
With showLabel: a quiet line under the label, read as its description.
disabled
boolean
onChange
(checked: boolean) => void
Called with the new value; from mixed, true.
className
string
CheckboxExample.swiftiOS · coming soon
CheckboxExample.ktAndroid · coming soon

Each token reads a role, and the role lands on a value in each theme. Change the role and every component that reads it follows.

Space and shape

check-mark-size
icon-size-sm
16px
check-inset
inset-2xs
2px
check-size
its own value
calc(16px + 2 * 2px)
check-radius
radius-xs
4px
check-edge-width
line-strong
2px
check-target
size-target
44px
check-active-scale
scale-press-sm
.94
check-disabled-opacity
opacity-disabled
0.35
checkrow-gap
gap-md
8px
checkrow-box-margin-top
its own value
calc((18px - calc(var(--check-mark-size) + 2 * var(--check-inset))) / 2)
checkrow-words-gap
gap-2xs
2px

Type

checkrow-font
text-caption
500 13px/18px "Funnel Sans", system-ui, -apple-system, "Segoe UI", sans-serif
checkrow-words-small-font
text-small
500 12px/16px "Funnel Sans", system-ui, -apple-system, "Segoe UI", sans-serif

Motion

check-duration
motion-fast
160ms
check-easing
ease-standard
cubic-bezier(0.2, 0, 0, 1)
check-active-duration
motion-instant
100ms
check-mark-settle-animation
motion-settle
sk-settle 240ms cubic-bezier(0, 0, 0, 1)

Checked automatically with axe-core 4.11.4 against WCAG 2.2 A and AA, on the live preview above, in light and dark, on 26 October 2026.

Result
No issues found, in light or in dark.
By design
A checkbox named by label that Tab reaches; Enter or Space flips it through onChange, and it is announced "checked", "not checked" or "mixed". disabled takes it out of the tab order. With showLabel the row is its label, so a click on the words flips it too, and the hint is its description. The mark is hidden; the state is said. In forced colours the edge and the mark draw in the system colours.
Not tested
The automated check doesn't test keyboard use, focus order or what a screen reader says. The lines above are the design system's own account of them.