Skip to content

Wheel Picker

Spin a drum of values to pick one: a time, a date or any short list, one column per part.

import { WheelPicker, WheelPickerColumn, WheelPickerOption } from '@youj/react'

Installation & usage

npm i @youj/react

For an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.

Use a wheel picker where spinning is the natural gesture: touch-first forms, a timer, an alarm, a short list to choose from in place. For a time typed at a keyboard, use a Time Picker; for a long list to search, a Select or a Combobox.

import { WheelPicker } from '@youj/react'

<WheelPicker
  aria-label="Alarm"
  columns={[
    { id: 'hour', label: 'Hours', options: hours },
    { id: 'minute', label: 'Minutes', options: minutes },
  ]}
  value={alarm}
  onValueChange={setAlarm}
/>

Each column is { id, label, options }, and each option { value, label, disabled? }. With columns, the value is an object of each column's value by its id. One column has a shorthand: options and label on the picker, with a plain string value.

<WheelPicker label="Fruit" options={fruit} defaultValue="cherry" />

It is uncontrolled with defaultValue (a column left out starts on its first row it can rest on), or controlled with value and onValueChange. The value is committed when a column is let go, so onValueChange fires once per spin, not on every row it passes.

size (sm, default or lg) sets the row height and type, following the theme's control heights. The picker keeps its size while it turns: each column is as wide as its widest label.

Reference

Propscolumns or options, value, loop, size, disabled, sound…

Props

WheelPicker props
PropType
columnsWheelPickerColumn[]

The wheels, start to end.

–
valueRecord<string, string>

The chosen value of each column, by column id. Controlled.

–
defaultValueRecord<string, string>

The starting values when uncontrolled. A column left out starts on its first row it can rest on.

–
onValueChange(value: Record<string, string>) => void–
optionsnever

One wheel's rows: the shorthand for a single column.

–
labelnever

The wheel's accessible name. Default "Value" (translated).

–
loopboolean

Every column wraps around: past the last row comes the first.

–
sizeWheelPickerSize
'default''sm''lg'

The row height and type size.

default `default`

`default`
disabledboolean

Blocks dragging, scrolling and keys, and takes the columns out of the tab order.

–
soundboolean

With sound on (YoujProvider’s sound), every row that passes the band ticks. false keeps it silent.

default true

true

Data attributes

The root carries data-size, plus data-yj-loop with loop and data-disabled when disabled. Each column is wheel-picker-column with data-id (its column id) and data-dragging while a pointer holds it; each drawn row is wheel-picker-item with data-index, and data-selected in the band and data-disabled when it can't be chosen. The band is wheel-picker-band.

MotionDrag, flick, wheel · reduced motion

Motion

Motion tokens
MomentTokens
Dragthe column follows the pointer 1:1; past either end it pulls less the further it goes
no token

Flickthe column coasts on with the flick’s momentum and decays onto the row it reaches
duration.slow

340ms

340ms
Let go past an endthe column springs back to the first or last row
spring.smooth

0.36s, bounce 0

0.36s, bounce 0
Key, click, wheel or new valuethe column turns to the row
spring.smooth

0.36s, bounce 0

0.36s, bounce 0
Rowsrows curve away on a drum and fade towards the top and bottom; the row in the band is full strength
no token

Reduced motion

A drag still follows the pointer. There is no momentum: a release, key, click or new value puts the row in the band at once.

Reduced motion

Dragging works the same. A release lands on the nearest row with no momentum, and keys, clicks and new values turn the column at once.

Sound

With sound on (YoujProvider's sound), every row that passes the band ticks. sound={false} keeps the picker silent.

AccessibilitySpinbuttons, keyboard, type-ahead

Accessibility

  • Each column is a spinbutton named by its label, its value text the chosen row's label, so a screen reader reads "Hours, 7" and each new row as it turns. Give the picker an aria-label too when it has more than one column; it is a group.
  • ArrowUp turns to the next row and ArrowDown to the one before; Page Up and Page Down turn five rows; Home and End go to the first and last. Disabled rows are skipped.
  • Typing jumps to the next row whose label starts with what was typed; the same letter again moves on to the next match.
  • ArrowLeft and ArrowRight move between columns, mirrored in right-to-left, where the columns run from the right too.
  • Each column is one tab stop with a visible focus ring. disabled takes the columns out of the tab order and sets aria-disabled.
  • Touch, pen and mouse drag; each pointer is captured, so a drag keeps going outside the column. The wheel turns the column under the pointer without scrolling the page.