vaiperui

components / time-picker

TimePicker

Field that opens hour and minute columns to choose a time of day.

How to use

The value is a string `"HH:mm"` in 24h (`"14:30"`), also with `hour12`, which only changes how it is shown and adds an AM/PM column. The panel stays open while you pick: choose the hour, then the minute, and close with `Esc` or by clicking outside. `minuteStep` limits the minutes (e.g. 15).

preview
TimePicker.tsx
import { TimePicker } from "vaiperui";
const [time, setTime] = useState("14:30");
<TimePicker label="Time" value={time} onValueChange={setTime} />
<TimePicker label="Time" hour12 minuteStep={15} />

24h and 12h

preview
24h
12h
hour-cycle.tsx
<TimePicker defaultValue="14:30" />
<TimePicker hour12 defaultValue="14:30" />

Minute step

preview
15
30
minute-step.tsx
<TimePicker minuteStep={15} />
<TimePicker minuteStep={30} />

Sizes

preview
sm
md
lg
sizes.tsx
<TimePicker size="sm" />
<TimePicker size="md" />
<TimePicker size="lg" />

States

preview
Extra detail
helper
This is required
error
disabled
read only
states.tsx
<TimePicker label="..." helperText="..." />
<TimePicker label="..." errorMessage="..." />
<TimePicker label="..." disabled />
<TimePicker label="..." readOnly />

Props

PropValuesDefaultDescription
value / defaultValuestringnoneTime of day as "HH:mm" (24h), controlled or initial
onValueChange(time: string) => voidnoneCalled with "HH:mm" when the hour, minute or period changes
hour12booleanfalse12h with an AM/PM column
minuteStepnumber1Granularity of the minutes column
labels{ hours, minutes, period, am, pm }noneTexts of the time columns
label / helperTextstringnoneLabel above the field and help text below
errorMessagestringnoneError text; its presence alone forces the error state
namestringnoneForm field name
disabled / readOnlybooleanfalseDoes not open the panel; readOnly still shows the value
variantring | outlineringFocus style, same as Input
size / radiussm | md | lg / none | sm | md | lg | fullmd / mdField height and corner rounding
aria-labelstringnoneAccessible name when there is no label
panelLabelstringnoneAccessible name of the open panel

Accessibility

  • The trigger is a `<button>` with `aria-haspopup="dialog"` and `aria-expanded` reflecting the panel state.
  • `label` is linked with `htmlFor`; without `label`, the trigger uses `aria-label`.
  • `errorMessage` sets `aria-invalid` and `aria-describedby` to the message; otherwise `helperText` is linked through `aria-describedby`.
  • The panel is a `role="dialog"` named by `panelLabel`, rendered in a portal on `document.body`.
  • `disabled` disables the button; `readOnly` keeps it focusable but the panel never opens.
  • Hours, minutes and (with `hour12`) AM/PM are columns, each a `role="group"` named by `labels.hours`, `labels.minutes` and `labels.period`.
  • Each option is a `<button>` with `aria-pressed` on the selected one; the selected option is scrolled into view when the panel opens.

Keyboard

KeyAction
Enter / SpaceOpens or closes the panel from the trigger.
Arrow Up / DownInside a column, moves focus to the previous / next option.
TabMoves between the options of the panel (every option is a tab stop).
Enter / SpacePicks the focused option; the panel stays open.
EscCloses the panel and returns focus to the trigger.

Keep in mind

  • Focus is not moved into the panel on open and the panel stays open after a pick: close it with Esc or by clicking outside.
  • Clicking outside closes the panel without moving focus back; only Esc restores focus to the trigger.
  • The time is only picked from the columns (no typing, no `minuteStep` outside the listed minutes). Pass `labels` and `panelLabel` translated; the defaults are in English.