Skip to content
Component installs need the registry setup
VegaStack Design

Time Field

A time of day as one typed field — "HH:mm" in and out, shown on the locale's 12- or 24-hour clock, stepped with the arrow keys.

Status
stable
Since
0.39.0
Accessibility pattern
APG spinbutton, typed entry, min/max

Last updated

Type “930” or “9:30 pm”, or use ↑ and ↓.

Install

Configure registry credentials first using the Quickstart. Before copying this item or any transitive registry dependency, verify the signed manifest and save the exact bytes for Time Field before installation. Retain the digest printed by this command.

pnpm exec vegastack-design verify time-field

Install with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.

pnpm dlx shadcn@4.21.0 add @vegastack/time-field

Run the exact offline --post-write command printed by the preflight, using its saved item path and independently retained --expected-integrity digest. Use the saved preflight for every transitive registry dependency too; stop on any mismatch before using the components.

The same command installs the registry items it composes: @vegastack/date-time, @vegastack/input-group.

Usage

import { TimeField } from "@/components/ui/time-field";

const [time, setTime] = useState<string | null>("09:00");

<TimeField aria-label="Time" value={time} onChange={setTime} />;

The value is always "HH:mm" on a 24-hour clock, whatever the field shows: hourCycle={12} shows "9:30 AM", 24 shows "09:30", and the locale decides when it is left out. Typing accepts "9", "930", "9:30", "9.30 pm" or "21:30"; Enter or leaving the field sets it. A typed time outside min/max moves inside, and text that is not a time is marked invalid until it is fixed or left, when the field shows the value again. Clearing the field sends null. min is not after max: an overnight window is not a range this field holds.

The field holds a wall-clock time, never an instant: say which zone it is in next to it (a hint, or the RecurrencePicker's time-zone line).

Scope

A time of day on its own: a schedule's "at", a reminder, opening hours. A date and a time together is the DateTimePicker; a duration is a NumberField with a unit.

Examples

Type “930” or “9:30 pm”, or use ↑ and ↓.

States

The 24-hour clock, a 30-minute step bounded by min and max, empty with a placeholder, invalid and disabled.

API Reference

PropTypeDefaultDescription
onChange*(value: string | null) => void—Fires with the new time ("HH:mm"), or null when the field is cleared.
value*string | null—The time, "HH:mm" on a 24-hour clock; null for none.
aria-describedbystring—Ids of the elements describing the field — a hint or an error.
aria-invalid"false" | "grammar" | "spelling" | "true" | boolean—Marks the field invalid (the destructive border).
aria-labelstring—The field's accessible name, when no <label> names it.
aria-labelledbystring—Ids of the elements naming the field.
classNamestring—Classes for the bordered box (its width: w-36 by default).
disabledbooleanfalseDisable the field.
hourCycle12 | 24—12 shows "9:30 AM", 24 shows "09:30". Omit it and the locale decides.
idstring—id for the input, so a Label htmlFor names it.
localestring"en-US"BCP-47 locale for the clock and for reading AM/PM words.
maxstring—The latest time, "HH:mm"; a later one is lowered to it. It is not before min: an overnight window ("22:00" to "06:00") is not a range this field holds.
minstring—The earliest time, "HH:mm"; an earlier one is raised to it.
placeholderstring—Text shown in the empty field.
refReact.Ref<HTMLInputElement>—Ref forwarded to the input.
stepnumber15Minutes the arrow keys move the time; it snaps to this grid (15 → :00, :15, :30, :45), so 23:45 is the last stop unless max says otherwise. A typed time may fall between steps.

Data attributes and CSS variables on TimeField

AttributeValues
data-slot"time-field"

Accessibility

  • The input is a spin button: its value is the minutes since midnight, read out as the formatted time ("9:30 AM"), with min and max as its bounds. Name it with a label or aria-label.
  • Text that is not a time sets aria-invalid while it is in the field.
KeyAction
↑ / ↓Step by step minutes, on the step's grid.
Page Up / Page DownStep by an hour.
Home / EndJump to min / max, when set and not typing.
EnterSet the typed time.
EscDrop what was typed; a second press reaches the host.
ContractStates tested
Behaviourdefault, empty, stepping, clamped, typed, invalid-draft, cleared, disabled, invalid
Accessibilitykeyboard, labeled, spinbutton, browser-accessibility-test
Visualdefault, focus, invalid, disabled, dark

Do / Don't

Do
Keep the value in HH:mm and let hourCycle or the locale decide how it looks.
Don't
Store the formatted text (9:30 AM) or a Date for a time of day.
Do
Say which time zone the time is in, next to the field.
Don't
Leave the zone implied when people in other zones read it.

On this page