TimeField
A typed time input — format-driven, with Arrow-key stepping and a native Date value.
@garn/ui/time-fieldOn this page
Default
A default 24-hour time field.
Twelve Hour
A 12-hour format (h:mm a).
Step
Arrow keys step by 15 minutes.
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-field- Registry
time-field- Deps
date-fnslucide-react- Registry deps
utilsdate-iouse-controllable-stateinput
Import
import { TimeField } from "@garn/ui/time-field";Props
The component's public props and their types.
appearance- Description
- No description.
- Type
"ghost" | "outline" | "soft"- Default
outline
size- Description
- No description.
- Type
"lg" | "md" | "sm" | "xl" | "xs"- Default
md
defaultValue- Description
- Initial time (uncontrolled).
- Type
Date
format- Description
- date-fns token string driving display, parse, and the placeholder.
- Type
string- Default
"HH:mm"
hourStep- Description
- Minutes added/removed per PageUp/PageDown.
- Type
number- Default
60
leading- Description
- Override the default leading clock glyph.
- Type
React.ReactNode
onValueChange- Description
- Fires with the committed time (or `undefined` when cleared).
- Type
(value: Date | undefined) => void
step- Description
- Minutes added/removed per ArrowUp/ArrowDown.
- Type
number- Default
1
value- Description
- Selected time as a `Date` (time portion meaningful; date portion preserved).
- Type
Date
Plus 316 inherited native <input> attributes.
Styling
Target these data-slots and states, and remap these tokens, to restyle without forking the component.
[data-slot="input"][data-slot="input-root"][data-slot="input-leading"][data-slot="input-clear"]States
| Selector | State | Description |
|---|---|---|
:hover | hover | Edge / fill grows slightly stronger. |
:focus | focus | Border darkens on any focus. |
:focus-visible | focus-visible | Keyboard focus adds a neutral ring. |
[aria-invalid=true] | invalid | Danger border + auto error glyph (from Input). |
[disabled] | disabled | Dims and blocks typing. |
::placeholder | placeholder | Muted placeholder = the format pattern. |
| Property | Token | Tier |
|---|---|---|
height | --garn-control-h-md | semantic · air |
paddingInline | --garn-control-px-md | semantic · air |
When to use
Enter a time of day by typing, with Arrow-key nudging — in a form where a slot list would be overkill.
Reach for something else when
- Picking from fixed slots (use TimePicker)
- a date + time together (use DateTimePicker)
- a duration (use a number field with a unit).
Overview
TimeField is the typed time control — the time-side twin of DatePicker's field. It reuses Input wholesale (the clock leading glyph, clear button, appearance × size, the wrapped focus surface, and the invalid treatment), adding only the time logic: a format prop (date-fns tokens like HH:mm or h:mm a) that drives display, parse, and the placeholder, and Arrow-key stepping (↑/↓ by step minutes, PageUp/Down by the hour). The committed value is a native Date — the time portion is what matters, the date portion is preserved — so a TimeField drops straight into DateTimePicker.
Reach for TimePicker instead when users should pick from a list of slots (booking), and DateTimePicker when a date and time are entered together.
Guidelines
Pair with a Label and pick a format that matches expectations (24-hour HH:mm vs 12-hour h:mm a); it drives typing and display together. Let the field surface own sizing — choose a size rung rather than hand-padding. Reflect validity with aria-invalid + a described message. Arrow keys step the value, so keyboard users never need the mouse.
Wire format: HH:mm:ss. With name, a hidden input posts the canonical time of day (the <input type="time" step="1"> wire format) — never the formatted display text ("9:30 AM") and never a UTC-shifted instant. The visible field carries no name, so nothing double-submits.
Best practices
- Pair with a label and choose a 24h/12h
format. - Hold the value in state (or use
defaultValue); read it as aDate. - Set
stepto the granularity you want Arrow keys to nudge.
- Don't rely on the placeholder as the label.
- Don't hand-set height/padding — the size variant owns the grid.
Content guidelines
- Label with what the time is for ("Start time").
- Use a
formatwhose pattern reads as an example ("HH:mm").
Troubleshooting
The value is a Date with today's date, not just a time.
Cause. TimeField stores a native Date; when empty it steps/parses onto today.
Fix. Read only the time from the value, or pass a value whose date you control.
Typed time snaps back.
Cause. The text didn't match format, so it reverted on blur.
Fix. Type in the exact format (shown as the placeholder).
Accessibility
- Role
textbox- Focus
- Inherits Input's focus surface (border darkens on focus; keyboard focus adds a neutral ring).
Accessibility requirements
Give every time field an accessible label.
when no associated <label htmlFor>, aria-label, or aria-labelledby
Keyboard
| ArrowUpArrowDown | Step the time by `step` minutes (default 1). |
| PageUpPageDown | Step the time by `hourStep` minutes (default 60). |
- Give the field an accessible label — <label htmlFor>, aria-label, or aria-labelledby; the format-pattern placeholder is not a label.
- The expected format is announced via a visually-hidden aria-describedby hint.
- Reflect validation with aria-invalid + a described error message.