TimePicker
A time field with a scrollable list of selectable time slots — the booking-UI pattern.
@garn/ui/time-pickerDefault
A time field with a 30-minute slot list.
Interval
A 15-minute slot interval.
Twelve Hour
A 12-hour format (h:mm a).
States
Disabled and invalid.
Installation
garn is copy-in — the garn CLI writes this component's source into your project, so you own and can edit it. It resolves any base files and installs the npm dependencies below for you.
npx garn-ui add time-picker- Registry
time-picker- Source
- time-picker.tsx
- Deps
date-fnsclass-variance-authoritylucide-react- Registry deps
utilsdate-iofield-surfaceuse-controllable-statedate-picker
Import
import { TimePicker } from "@garn/ui/time-picker";Props
Generated from the component's TypeScript types.
appearance- Description
- No description.
- Type
"ghost" | "outline" | "soft"- Default
outline
size- Description
- No description.
- Type
"lg" | "md" | "sm" | "xl" | "xs"- Default
md
"aria-invalid"- Description
- No description.
- Type
"false" | "true" | boolean
"aria-label"- Description
- No description.
- Type
string
"aria-labelledby"- Description
- No description.
- Type
string
className- Description
- No description.
- Type
string
clearable- Description
- No description.
- Type
boolean
clearLabel- Description
- No description.
- Type
string
contentClassName- Description
- No description.
- Type
string
defaultOpen- Description
- No description.
- Type
boolean
defaultValue- Description
- No description.
- Type
Date
disabled- Description
- No description.
- Type
boolean
format- Description
- date-fns token string driving display, parse, and the placeholder.
- Type
string
id- Description
- No description.
- Type
string
interval- Description
- Minutes between slots in the list. Default 30.
- Type
number
name- Description
- Name for a hidden input mirroring the value on form submit as a plain `HH:mm:ss` time of day (the `<input type="time" step="1">` wire format — never an invented-date UTC instant).
- Type
string
onOpenChange- Description
- No description.
- Type
(open: boolean) => void
onValueChange- Description
- No description.
- Type
(value: Date | undefined) => void
open- Description
- No description.
- Type
boolean
placeholder- Description
- No description.
- Type
string
readOnly- Description
- No description.
- Type
boolean
ref- Description
- No description.
- Type
React.Ref<HTMLInputElement>
required- Description
- No description.
- Type
boolean
triggerLabel- Description
- No description.
- Type
string
value- Description
- No description.
- Type
Date
Plus 3 inherited native <input> attributes.
Styling
Target these data-slots and remap these tokens to restyle without forking the component.
[data-slot="time-picker"][data-slot="time-picker-input"][data-slot="time-picker-trigger"][data-slot="time-picker-clear"][data-slot="time-picker-option"]| Property | Token | Tier |
|---|---|---|
height | --garn-control-h-md | semantic · air |
paddingInline | --garn-control-px-md | semantic · air |
Overview
TimePicker is TimeField's pick-from-a-list sibling — the booking-UI pattern. It's the same typeable field, plus a Popover holding a scrollable listbox of time slots at your chosen interval (default 30 min). It reuses the date family's field + popover shell (with a clock trigger), so it lines up with DatePicker and the rest of the form surface. The value is a native Date; picking a slot preserves the value's date, and typing stays available alongside the list.
Use TimeField when a bare typed time is enough (no slots), and DateTimePicker for a date and time together.
Guidelines
Choose an interval that matches the booking granularity (15/30 min). Pair with a Label and pick a 24h/12h format. The typed field is fully keyboard-operable (ArrowDown opens, Enter commits, Escape closes); the slot list is keyboard-operable too (arrows/Home/End/Page roving focus, Enter/Space commits), with the current time marked and scrolled into view on open. Reflect validity with aria-invalid.
Wire format: HH:mm:ss. With name, the hidden form input posts the canonical time of day (the <input type="time" step="1"> wire format) — a time-only field never posts an invented-date UTC instant.
Best practices
- Set
intervalto the slot granularity; pair with a label. - Hold the value in state; read it as a
Date. - Reflect validation with aria-invalid + a described message.
- Don't rely on the placeholder as the label.
- Don't use huge slot lists at 1-minute intervals — prefer TimeField for fine typing.
Content guidelines
- Label with what the time is for ("Appointment time").
Accessibility
- Role
textbox- ARIA APG
- combobox
- Focus
- Field focus surface as the date pickers; the list is opened from the field and dismissed on Escape/outside.
Enforceable contracts
Give every time picker an accessible label.
when no associated <label htmlFor>, aria-label, or aria-labelledby
Keyboard
| ArrowDown | From the field: open the slot list. In the list: move the roving focus to the next slot (clamped at the end, no wrap). |
| ArrowUp | In the list: move the roving focus to the previous slot (clamped at the start, no wrap). |
| Home | In the list: jump the roving focus to the first slot. |
| End | In the list: jump the roving focus to the last slot. |
| PageDown | In the list: jump the roving focus forward ~one hour of slots. |
| PageUp | In the list: jump the roving focus back ~one hour of slots. |
| Enter | From the field: commit the typed time. In the list: commit the focused slot (select + close + return focus to the field). |
| Space | In the list: commit the focused slot (select + close + return focus to the field). |
| Escape | Close the slot list. |
- • Give the field an accessible label; the format-pattern placeholder is not a label.
- • The slot list is a role=listbox of role=option slots; the current time carries aria-selected and scrolls into view on open.
- • The slot list is fully keyboard-operable: a roving tabindex keeps exactly one option tabbable at a time; arrows/Home/End/PageUp/PageDown move the roving focus, and Enter/Space commits the focused slot. The list is opened from the field and returns focus there on commit.
- • Type-ahead (printable-character jump within the list) is intentionally omitted — the typed field is the primary character-entry path; a deliberate deviation from the APG Listbox pattern.
- • Reflect validation with aria-invalid + a described message.
- Status
- Stable
- Version
v0.3.0- Introduced
v0.2.0
garn doesn't yet record a per-release version history. This reflects the component's current lifecycle state; full changelog entries will appear once the metadata pipeline emits a lifecycle.history field.