Components
Wheel Picker
People choose a date or a time by scrolling columns, the usual pattern on phones for a time or a nearby date. In Arabic the day sits on the right and ص / م on the left, while hour and minute keep clock order; days and months carry their Arabic names.
Preview
Mirrored is the same English content laid out right to left: it separates a mirroring bug from a translation one.
Installation
npx ritla add wheel-pickerAlso works with shadcn: npx shadcn@latest add @ritla/wheel-picker
Installing by hand copies the component's source into your project. The source comes with a Ritla UI license, so the CLI is the short way to the same files.
Usage
import { WheelDateTimePicker } from "@/components/ui/wheel-picker";const [value, setValue] = useState(() => nextFiveMinutes());
<WheelDateTimePicker value={value} onValueChange={setValue} minuteStep={5} />Examples
Date
WheelDatePicker: day, month and year, the day first in reading order. Months are short in English and written out in Arabic; years run five either side of the value by default (minYear, maxYear). For a birthday, type the date in a field rather than scroll through years.
In a bottom sheet, from a field
The field shows the value and opens a bottom sheet (Drawer) titled with the field's label. Done keeps the new value; Cancel, the close button or a swipe down leave it as it was. Don't leave a wheel open inline on a page.
API reference
WheelDateTimePicker
| Prop | Type | Default | Description |
|---|---|---|---|
value / onValueChange | Date / (value: Date) => void | None | The value, always a full date and time. Open on a sensible one: today, the next full five minutes. |
min / max | Date | today / 60 days later | The first and last day offered. Limit them to what is valid, such as no past days for a delivery. |
minuteStep | number | 5 | Minutes in steps of this. |
hourCycle | "h12" | "h23" | "h12" | 12 hours with the AM / PM column, or 24 hours without it. |
labels | { group, day, hour, minute, period } | None | Names for the group and columns, for screen readers. Default: in the page language; repeat the field's label in group. |
locale / numerals | string / "latn" | "arab" | None | Default: from KanzProvider. Names come from Intl in the page language, and every column uses one digit system. |
WheelDatePicker
| Prop | Type | Default | Description |
|---|---|---|---|
value / onValueChange | Date / (value: Date) => void | None | A day past the end of the new month moves to its last day (31 May, then June: 30 June). |
minYear / maxYear | number | value ± 5 | The years offered. |
labels | { group, day, month, year } | None | Names for the group and columns. |
WheelPicker / WheelPickerColumn
| Prop | Type | Default | Description |
|---|---|---|---|
options | { value, label, valueText?, disabled? }[] | None | The column's rows. valueText is what a screen reader says; disabled rows stay visible and the column skips them (a booked slot). |
value / onValueChange | string / (value: string) => void | None | The value of the row in the band. |
label | string | None | The column's name ("Hour"). |
aria-label (WheelPicker) | string | None | The whole picker's name; repeat the field's label. |
Accessibility
Keyboard
Keys are written for left-to-right. In right-to-left the arrow keys swap: the one pointing toward the reading direction moves forward.
| Key | Action |
|---|---|
| Tab | From one column to the next: in the date and time picker, day, hour, minute, then AM / PM. |
| ArrowUpArrowDown | One row up or down in the column. |
| PageUpPageDown | Five rows. |
| HomeEnd | The first and last row. |
ARIA
- Each column is a
spinbuttonwith its name and its value in words inaria-valuetext("Wednesday 30 December"), never the band's color alone. - The columns are a group named after the field. The rows are the picture only, hidden from screen readers.
- Scrolling uses the platform's own momentum and settles by CSS scroll snapping; with reduced motion the column jumps instead of gliding.
- Scrolling is hard for some people: also let them type the value in the field.
Arabic and RTL notes
Mirrors
- The column order: the day on the right, where the Arabic reader starts, then the time, with ص / م on the left.
Does not mirror
- Scrolling is vertical: up and down don't change and the rows inside a column don't flip.
- Hour, then minute, left to right in every language, as on a clock; only AM / PM moves to the other side. Tab follows the pair: hour, then minute.
- Numerals
- Every column uses one digit system from
numerals: "6 25" or "٦ ٢٥", never both in one picker. - Arabic typography
- Rows are
body-24Semibold, 48px tall; long Arabic names ("الأربعاء 30 ديسمبر") widen the column rather than wrap. - Mixed-direction text
- Arabic month names come from Intl (يناير to ديسمبر), the same set as the date pickers and the rest of the product.