Time Field
betaA 24-hour time, typed or picked from a list under the field. "19", "1930", "19.30", and "7pm" all settle as 19:00-style times, and an end time shows how long it makes the event.
Preview
start and end
size
states
Install
1. Add the registry to
components.json, once per app. Without it, the shadcn CLI looks up@txbrendanin a public list that txbrendan/ui does not control.npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.
npx shadcn@latest add @txbrendan/theme3. Add the component.
npx shadcn@latest add @txbrendan/time-field
Use it for
- A start or end time for a meeting, a session, or a booking. Pair it with a DatePicker for a date and a time.
- Give it a label, like "Starts". Without one, pass aria-label.
- The value is "HH:MM" on a 24-hour clock, or "" for none. onValueChange only ever gets a good time.
- Typed text settles when the field is left or Enter is pressed. Text it cannot read says so in red, and the last good time stays.
- For an end time, pass the start as from. The list begins after it, each time says how long that makes it, and the field shows the length.
- step sets the minutes between listed times. Any minute can still be typed. min and max default to 06:00 and 23:00.
Avoid it for
- A length of time, like 90 minutes. Use a NumberStepper with a duration UnitSwitch.
- A date. Use a DatePicker.
- Times that cross midnight. The value has no date, so 23:30 to 00:30 cannot be told from a backwards range.
Props
- label
Names the field, above it.
ReactNode
- value
The time, "HH:MM", or "" for none. Use it with onValueChange.
string
- defaultValue
The time at first render, when you do not control it.
string · default ""
- onValueChange
Called with a good "HH:MM" once the field settles, or "" when cleared.
(time: string) => void
- from
The start, for an end time. The list begins after it and says each length.
string
- step
Minutes between listed times.
number · default 15
- min
The earliest time allowed.
string · default "06:00"
- max
The latest time allowed.
string · default "23:00"
- placeholder
Shown while the field is empty.
string · default "--:--"
- hint
A short note under the field.
ReactNode
- error
Replaces the hint, in red. The field's own reading problems show first.
ReactNode
- size
Height and text size of the input, the label, and the list.
"sm" | "md" | "lg" · default "md"
- disabled
Blocks typing and the list.
boolean · default false
- defaultOpen
Opens the list on first render, for docs and screenshots.
boolean · default false
- name
Form field name. Sends the settled time.
string
- className
Classes for the frame. Later classes win.
string
| Prop | Type | Default | What it does |
|---|---|---|---|
| label | ReactNode | - | Names the field, above it. |
| value | string | - | The time, "HH:MM", or "" for none. Use it with onValueChange. |
| defaultValue | string | "" | The time at first render, when you do not control it. |
| onValueChange | (time: string) => void | - | Called with a good "HH:MM" once the field settles, or "" when cleared. |
| from | string | - | The start, for an end time. The list begins after it and says each length. |
| step | number | 15 | Minutes between listed times. |
| min | string | "06:00" | The earliest time allowed. |
| max | string | "23:00" | The latest time allowed. |
| placeholder | string | "--:--" | Shown while the field is empty. |
| hint | ReactNode | - | A short note under the field. |
| error | ReactNode | - | Replaces the hint, in red. The field's own reading problems show first. |
| size | "sm" | "md" | "lg" | "md" | Height and text size of the input, the label, and the list. |
| disabled | boolean | false | Blocks typing and the list. |
| defaultOpen | boolean | false | Opens the list on first render, for docs and screenshots. |
| name | string | - | Form field name. Sends the settled time. |
| className | string | - | Classes for the frame. Later classes win. |
Accessibility
- The input is a combobox. It says it has a list, and whether the list is open.
- Focus stays in the input. The highlighted time is its active descendant, so screen readers read it as the keys move.
- Typing, a click, or the down arrow opens the list at the nearest time. The arrows move a step, and Page Up, Page Down, or Shift with an arrow move an hour.
- Enter takes the highlighted time once the keys have moved it. Otherwise it takes the typed one. Home and End stay in the text.
- The first Esc closes the list, and the next puts back the last good time. With nothing to undo, Esc goes on to the page.
- A problem reading the time is shown in red under the field and announced once. The input is marked invalid.
- For an end time, the length after the time describes the input.
Examples
A start and an end
const [start, setStart] = useState("19:00");
const [end, setEnd] = useState("20:30");
<TimeField label="Starts" value={start} onValueChange={setStart} />
<TimeField label="Ends" value={end} onValueChange={setEnd} from={start} />Every 30 minutes, in the evening
<TimeField label="Workshop" step={30} min="17:00" max="21:00" />