Time Picker
Time input — styled wrapper around the native <input type='time'>. Value is a 'HH:MM' (or 'HH:MM:SS' with step=1) string. The browser provides 12/24-hour locale handling, keyboard arrow spinning, and screen-reader announcement.
Installation
pnpm dlx @hex-core/cli add time-pickerUsage
import { Time Picker } from "@/components/ui/time-picker"Basic time picker
Bind a string state and render the picker
import { useState } from "react";
import { TimePicker } from "@/components/ui/time-picker";
export function Example() {
const [time, setTime] = useState<string>();
return <TimePicker value={time} onChange={setTime} aria-label="Meeting time" />;
}With seconds + 5-minute step
step={1} adds a seconds segment; step={300} steps minutes by 5 in browsers that support it
import { useState } from "react";
import { TimePicker } from "@/components/ui/time-picker";
export function Example() {
const [time, setTime] = useState<string>("09:00");
return <TimePicker value={time} onChange={setTime} step={300} min="09:00" max="17:00" aria-label="Working hours start" />;
}API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | Controlled time value as 'HH:MM' or 'HH:MM:SS' (24-hour). |
onChange | function | — | Callback when the user picks a time: (value: string) => void. Value is always 24-hour 'HH:MM' (or 'HH:MM:SS' when step=1). |
step | number | — | Step in seconds. Use 60 (default) for HH:MM, 1 for HH:MM:SS, 300 for 5-minute steps, 900 for 15-minute. Note: Chrome/Edge picker dropdown UI does NOT visually snap minutes to the step — it only affects keyboard arrow stepping and form validation. Off-step values are still rejected. |
min | string | — | Earliest selectable time as 'HH:MM'. |
max | string | — | Latest selectable time as 'HH:MM'. |
disabled | boolean | false | Disable the input. |
aria-label | string | — | Accessible label — required when no adjacent visible <label> is used. |
AI Guidance
When to use
Use for selecting a time of day in a form (meeting time, reminder, business hours start/end). Browser handles 12/24-hour locale automatically based on user system settings; the wire format is always 24-hour 'HH:MM'. Component is controlled-only — pair `value` with `onChange`; for uncontrolled use, the input behaves as a controlled empty input with no upstream state.
When not to use
Don't use for durations (hours/minutes elapsed) — use composite Input fields. Don't use for date+time together — combine with DatePicker. Don't use when explicit AM/PM segments matter for layout — compose Input + Select yourself; the native input renders AM/PM only in 12-hour locales. Don't use when you need pixel-identical chrome across browsers — the dropdown indicator is webkit-styled (Chrome/Edge/Safari) and absent in Firefox.
Common mistakes
- Passing a Date object to value — must be a string in 'HH:MM' format
- Treating onChange as 12-hour — the value is always 24-hour, the browser only displays it in user locale
- Missing aria-label when there's no adjacent visible <label> — input gets no accessible name
- Setting step to a value that doesn't divide evenly into 3600 (e.g. step=7) — confusing UX in browsers that snap to it
- Expecting Chrome/Edge's picker dropdown to visually skip minutes per step — it doesn't. The minute scroll wheel always shows every minute; step only constrains keyboard arrow stepping and form validation
- Setting min/max with seconds when step!=1 — the seconds segment is hidden, so users can't actually reach values that need it
- Expecting the dropdown picker indicator to render in Firefox — only webkit browsers paint it; Firefox renders the input without that affordance
- Expecting an uncontrolled mode — TimePicker is controlled-only. Pass both value + onChange, or omit both and accept that user input has no place to live
Accessibility
Native <input type='time'> — assistive tech announces hour/minute (and seconds if shown) segments individually. Arrow keys spin each segment. Disabled state is announced. Pass aria-label or wrap with a <label htmlFor>. Visual chrome (icon, dropdown affordance) is browser-controlled and varies across Chrome/Edge/Safari (rendered) and Firefox (absent).
Related components
Token budget: 700