Skip to content

TimeField

A typed time input — format-driven, with Arrow-key stepping and a native Date value.

Stablev0.4.2added in v0.2.0@garn/ui/time-field
On 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.

terminal
npx garn-ui add time-field
First time? Set up garn in your project
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

SelectorState
:hoverhover
:focusfocus
:focus-visiblefocus-visible
[aria-invalid=true]invalid
[disabled]disabled
::placeholderplaceholder
PropertyTokenTier
height--garn-control-h-mdsemantic · air
paddingInline--garn-control-px-mdsemantic · 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

Do
  • Pair with a label and choose a 24h/12h format.
  • Hold the value in state (or use defaultValue); read it as a Date.
  • Set step to the granularity you want Arrow keys to nudge.
Don't
  • 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 format whose 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

warn

Give every time field an accessible label.

when no associated <label htmlFor>, aria-label, or aria-labelledby

Keyboard

ArrowUpArrowDownStep the time by `step` minutes (default 1).
PageUpPageDownStep the time by `hourStep` minutes (default 60).
A1.3.1Info and RelationshipsA2.1.1KeyboardAA2.4.7Focus VisibleA4.1.2Name, Role, Value
  • 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.