cupertino-datetime-picker

Wheel and SegmentedControl

One column of an iOS picker, and the AM/PM control, exported for building pickers of your own.

Wheel

One column of an iOS picker. Scrolling is native with snap points, so touch and trackpad flings come from the platform; mouse drag has its own deceleration; a click on any row picks it; the keyboard steps with the arrows and jumps on typed digits. Rows tilt away from the centre and fade at the edges. The centre row is the value.

import { Wheel, WheelHighlight } from "@/components/ui/cupertino/wheel";

const minutes = Array.from({ length: 60 }, (_, m) => ({
  value: m,
  label: String(m).padStart(2, "0"),
}));

<div className="cdp cdp-wheel-mask relative flex justify-center">
  <WheelHighlight />
  <Wheel
    options={minutes}
    value={minute}
    onChange={setMinute}
    loop
    aria-label="Minute"
    className="w-16"
  />
</div>;

A group of wheels shares one WheelHighlight, the translucent bar behind the centre row, positioned by a relative container. cdp-wheel-mask fades the top and bottom rows.

PropTypeDefault
options{ value: number; label: string }[]The rows, top to bottom.
valuenumberThe centred option's value.
onChange(value: number, rows: number) => voidCalled when the wheel settles on a new row.
loopbooleanfalseWrap around, like the hour and minute wheels.
itemHeightnumber32Row height in px. Pass the same to WheelHighlight.
visibleRowsnumber5Rows shown; the wheel is this many rows tall.
aria-labelstringRequired. The wheel is a spinbutton.
classNamestringSet the width here.

onChange fires once per settle, not per pixel, and not when the wheel comes to rest where it already was. rows is how far the wheel turned, so a looped wheel can tell 11 → 12 from 1 → 12. Set value in onChange, as with a controlled <input>; a wheel whose value stays as it was turns back to it.

SegmentedControl

The iOS segmented control: a translucent track and a thumb that slides to the chosen segment. Underneath it is a native radio group, so arrow keys, focus and form semantics come from the browser.

import { SegmentedControl } from "@/components/ui/cupertino/segmented-control";

<SegmentedControl
  options={[
    { value: "am", label: "AM" },
    { value: "pm", label: "PM" },
  ]}
  value={period}
  onChange={setPeriod}
  aria-label="Day period"
/>;
PropType
options{ value: T; label: string }[]T is a string, number or boolean.
valueTThe chosen segment.
onChange(value: T) => void
aria-labelstringRequired. Names the radio group.
classNamestring

On this page