Choice list

● Stable · v0.1

A vertical stack of large, tappable options for picking one — "What feels hard?", "Who bought?", a category. Each option is a calm white pill-card; the chosen one lifts onto a soft teal tint with a check, so the selection reads by shape and colour, not a tiny dot.

In context

9:41

Try it

Multi-select — tap to toggle. "Charcoal" is disabled.

Purpose

Choice list is the friendly, first-run way to make one decision from a short set. It's bigger and calmer than a radio list and quieter than a grid of buttons — sized for a thumb and for a user who reads a screen by shape before words. Selection is obvious at a glance: the picked row turns a soft teal with a check, the rest stay plain white.

Anatomy

  • Option — a full-width white pill-card, at least 56dp tall. Optional leading icon (mono, outline), a label, and an optional one-line description.
  • Selection — the chosen option(s) sit on a soft teal tint with a teal edge and a filled check; the label goes semibold. This is the one licensed brand moment on the screen.
  • Disabled — a dimmed option that doesn't respond, for a choice that isn't available yet.

Code

import { ChoiceList } from '@daytwo/components'
// Single-select — value is a string
const [pick, setPick] = useState('money')
<ChoiceList
options={[
{ value: 'money', label: "Can't track my money", icon: <Wallet /> },
{ value: 'owed', label: 'People owe me', icon: <Banknote /> },
{ value: 'save', label: 'Hard to save', icon: <Receipt /> },
]}
value={pick}
onChange={setPick}
/>
// Multi-select — value is a string[]; toggle in onChange
const [picked, setPicked] = useState(['drinks'])
<ChoiceList
multiple
options={cats}
value={picked}
onChange={(v) =>
setPicked((prev) =>
prev.includes(v) ? prev.filter((x) => x !== v) : [...prev, v],
)
}
/>

For Burmese or Thai labels, pass fontFamily so the labels render in the right script.

When to use

  • A single decision from 2–6 options, especially first-run and onboarding.
  • When each option benefits from an icon or a short description — richer than plain radio buttons.
  • Multi-select tag/category pickers where each row is a peer.

When NOT to use

  • Switching a view of the same screen — that's a segmented control.
  • Long lists (past ~6) — use a searchable list or dropdown; a wall of pill-cards stops being scannable.
  • An on/off setting — use a switch.

Edge cases

Long Burmese/Thai label
Label wraps to two lines; row grows to fit
WhyMY/TH run longer than EN — the card must not clip the text
No selection yet
Render with an empty value — every row stays plain white
WhyA first-run step may start with nothing chosen
Disabled option
Pass disabled — the row dims and ignores taps
WhyA choice that isn't available yet shouldn't look tappable
Dark mode
Cards and the teal tint follow the theme surfaces
WhyThe selected lift stays legible in both schemes
Selection you can see across the room

The chosen row changes background, border, weight, and gains a check — four cues, not one. That redundancy is deliberate: a low-literacy user shouldn't have to hunt for a small dot to know what they picked.

Tokens used

color.surface.primarycolor.brand.tintcolor.brand.primarycolor.border.defaultcolor.text.primarycolor.text.secondaryradius.xltypography.body