Listbox

Atom
A custom select whose options stay on screen: single or multi-select, an optional detail line under each option, and a checkmark on the chosen items. Reach for it when the options need rich rows; prefer Select for a simple short list.

Playground

Every control sets a real prop of ListboxProps. The JSX below updates as you go.
Backend
Frontend
Design
Props
multi
boolean
bordered
boolean
selected
number
1
Size
pick one
disabled
boolean
JSX · ListboxProps
<Listbox bordered selected={1} />

Variants

6 examples from src/atoms/listbox/listbox.md, each rendered live from its own JSX.
Multi
<Listbox
accessibilityLabel="Teams"
items={[
{ label: "Backend", selected: true },
{ label: "Frontend" },
{ label: "Design", selected: true }
]}
multi
/>
Backend
Frontend
Design
Small
<Listbox
accessibilityLabel="Teams"
items={[
{ label: "Backend", selected: true },
{ label: "Frontend" },
{ label: "Design" }
]}
small
/>
Backend
Frontend
Design
Large
<Listbox
accessibilityLabel="Teams"
items={[
{ label: "Backend", selected: true },
{ label: "Frontend" },
{ label: "Design" }
]}
large
/>
Backend
Frontend
Design
Disabled
<Listbox
accessibilityLabel="Teams"
items={[
{ label: "Backend", selected: true },
{ label: "Frontend" },
{ label: "Design" }
]}
disabled
/>
Rachel Chen
rachel@acme.io
Ada Lovelace
ada@acme.io
Kevin Turner
kevin@acme.io
Detail line
<Listbox
accessibilityLabel="People"
items={[
{ label: "Rachel Chen", detail: "rachel@acme.io", selected: true },
{ label: "Ada Lovelace", detail: "ada@acme.io" },
{ label: "Kevin Turner", detail: "kevin@acme.io" }
]}
/>
xs step, pinned to the start (320)
Frontend
lg step, pinned to the start (512)
Frontend
Bare, in an lg Container: fills it (512)
Frontend
Measure
<Column snug>
<Listbox xs start bordered items={[{ label: "xs step, pinned to the start (320)", selected: true }, { label: "Frontend" }]} />
<Listbox lg start bordered items={[{ label: "lg step, pinned to the start (512)", selected: true }, { label: "Frontend" }]} />
<Container lg start><Listbox bordered items={[{ label: "Bare, in an lg Container: fills it (512)", selected: true }, { label: "Frontend" }]} /></Container>
</Column>

Sizes

Backend
Frontend
Design
small
Backend
Frontend
Design
default
Backend
Frontend
Design
large

States

Hover, focus and press are pinned so they can be compared side by side; the rest are real props.
Backend
Frontend
Design
default
Backend
Frontend
Design
focus
Backend
Frontend
Design
disabled

Anatomy

Backend
Frontend
Design
1
Listbox
2
Option
3
Selected check
4
Detail line
Dimmed parts appear only in some states or variants.

Props

13 props on ListboxProps, grouped as they are declared in source. Required props are marked *.
Prop
Type
Default
Description
Props
4 props
items *
ListboxItem[]
–
The options to render, top to bottom.
accessibilityLabel
string
–
Accessible name of the option list or multi-select checkbox group. Defaults to "Options".
multi
boolean
false
Multi-select: each row is a checkbox (a leading indicator on the web and Android, a trailing ✓ on iOS) instead of a single choice.
bordered
boolean
false
Wrap the list in a rounded, bordered content card with static glass in glass mode.
Size
pick one
9 props
small
boolean
false
Tighter rows with smaller text.
large
boolean
false
Taller rows.
disabled
boolean
false
Dim the list and block selection.
selected
number | number[]
–
Selected option index(es) (CONTROLLED): a single index in single-select, an array of indices in multi-select. Omit for uncontrolled use.
defaultSelected
number | number[]
–
Initial selection for uncontrolled use (a bare listbox selects on press). Falls back to the selected flags on items when omitted.
onChange
(selected: number | number[]) => void
–
Fired with the full new selection (an index in single-select, an index array in multi).
onSelect
(index: number) => void
–
Fired with the pressed option's index.
testID
string
–
E2E hook forwarded to the root list view.
style
LayoutStyle
–
Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds.
MeasureProps · width steps xxxs 192 · xxs 256 · xs 320 · sm 384 · md 448 · lg 512 · xl 576 · xxl 672 · xxxl 768 · wide 896 · wider 1024 · widest 1152 · page 1280, plus start to pin to the leading edge.

Accessibility and keyboard

Keyboard
ArrowUp
ArrowDown
Moves the active option
Space
Selects, toggles when multi
Home
End
First · last option
A letter
Jumps to the next match
Semantics
Roles
checkbox
group
multi
option
ARIA on web
aria-checked
aria-disabled
aria-hidden
aria-label
aria-selected
Below the platform minimum, the touch area grows to 44 pt on iOS and 48 dp on Android without moving the layout.
From the docs
Single-select exposes a list of selectable options. With multi, it exposes a checkbox group: each row owns its label, checked state, and tap target. Give either mode a meaningful accessibilityLabel; the fallback is "Options". Arrow keys and Home/End move focus within one tab stop. Single-select follows focus, while multi-select keeps the current selection until Enter or Space toggles the focused row.

Platform skins

The playground's props on each platform: same tree, native shapes, type and touch sizes.
Rachel Chen
rachel@acme.io
Ada Lovelace
ada@acme.io
Kevin Turner
kevin@acme.io
Web
control 10 · field 10 · card 16 · Instrument Sans
Rachel Chen
rachel@acme.io
Ada Lovelace
ada@acme.io
Kevin Turner
kevin@acme.io
iOS
capsule controls · field 8 · card 12 · SF Pro · 44 pt
Rachel Chen
rachel@acme.io
Ada Lovelace
ada@acme.io
Kevin Turner
kevin@acme.io
Android
Material 3 stadium · field 4 · card 12 · Roboto · 48 dp

Usage

<Listbox
accessibilityLabel="Teams"
items={[
{ label: "Backend", selected: true },
{ label: "Frontend" },
{ label: "Design" }
]}
/>
The list fills the parent it is given; a step of its own (xs, lg, …, with start to pin it to the leading edge) or a Container step sets its measure.
Neither iOS nor Material 3 ships a listbox, so every platform draws the Canvas v4 list, in its own face: a chosen row sits on the accent's soft wash with a 16px check at its end in the accent ink, in single and multi select alike, and the check's slot is kept on every row so a choice never reflows a label.
The rows are 10px in with a 500 14/18.2px label (13px small) over a 400 12/16.2px detail in the subtle ink, rounded concentric inside a bordered list, which is the popover fill behind the field-border hairline at the tile corner with a 4px inset. A pressed row takes the muted wash, and on the web a row under the pointer takes the hover wash at once.

Tokens

Read from the playground render. Values follow the scheme and the ion hue.
--fd
--ft
--ion-soft
--ion-tx
--ln2
--sf2
--tx3

Related components