WheelPicker
iOS-style drum picker — a column of options that scrolls and snaps to a selection
Category: inputs · WCAG 2.2-AA · inputs, picker, wheel, drum, scroll-snap, mobile, ios
States
- idle
- focused
- scrolling
Props
| Prop | Type | Default | Description |
|---|---|---|---|
options | WheelPickerOption[] | — | The rows of the wheel, in order |
value | string | — | The selected option value; the component is controlled |
onValueChange | (value: string) => void | — | Called with the new value when the wheel settles or a key moves the selection |
visibleCount | number | 5 | How many rows are visible. Odd numbers keep the selection centred. |
itemHeight | number | 36 | Row height in px. |
ariaLabel | string | — | Accessible label for the wheel — required when it has no visible label |
className | string | — | Additional CSS class names merged onto the root element. |
Design tokens
--cascivo-color-foreground--cascivo-color-text-muted--cascivo-border-subtle--cascivo-text-ui--cascivo-font-semibold--cascivo-radius-md--cascivo-ring-width--cascivo-ring-color
When to use
- Touch surfaces picking one value from a short, ordered, homogeneous range — an hour, a minute, a unit
- Mobile forms following the platform convention of a drum rather than a dropdown
- Inside a BottomSheet or ActionSheet, where a native select would open a competing overlay
When not to use
- Long or unordered option lists, where scrolling to a value is slower than typing — use Combobox
- Desktop, pointer-first forms — use Select or NativeSelect
- Dates, which have their own affordances — use DatePicker or Calendar
How to build an accessible WheelPicker in React →
Examples
Basic
Controlled — the caller owns the value.
<WheelPicker
ariaLabel="Hour"
value={hour}
onValueChange={setHour}
options={[
{ value: '09', label: '09' },
{ value: '10', label: '10' },
{ value: '11', label: '11' },
]}
/>Taller wheel
Shows more rows around the selection.
<WheelPicker ariaLabel="Minute" value={minute} onValueChange={setMinute} options={minutes} visibleCount={7} />Several columns
Compose one wheel per field; each column is independently operable.
<Flex gap="0">
<WheelPicker ariaLabel="Hour" value={hour} onValueChange={setHour} options={hours} />
<WheelPicker ariaLabel="Minute" value={minute} onValueChange={setMinute} options={minutes} />
</Flex>Related components
- Select — Pointer-first single selection on desktop
- TimePicker — Purpose-built time entry rather than a generic wheel
- BottomSheet — The usual host for a wheel on a phone screen