Time Confirm

Asks AM or PM when a typed time is ambiguous and catches times a clock change skips or repeats, so meetings aren't 12 hours off.

StopsMeeting booked 12 hours off

Installation

pnpm dlx shadcn@latest add https://bollard-ui.vercel.app/r/time-confirm.json

The files land in your project. They're yours to edit.

Usage

time-confirm-usage.tsx
import { TimeConfirm } from "@/components/ui/time-confirm"

<TimeConfirm label="Launch call" date="2026-10-13" timeZone="Asia/Kolkata" zones={zones} defaultValue="12" />

What it catches

  • Hours 1–12 typed without AM/PM
  • Noon and midnight, spelled out
  • Times skipped by a DST change, with a move to the first real time
  • Times repeated by a DST change, with a choice of which
  • Other zones' times, flagged when they fall on another day

Props

TimeConfirm props
PropTypeDefault
label

Visible label. The entry zone's city is added after it.

string"Time"
hint

Helper text shown under the field while it is empty.

string—
date* (required)

The calendar day this time is on, as YYYY-MM-DD. Needed for weekday and DST math.

string—
timeZone* (required)

IANA zone the time is entered in, e.g. "Asia/Kolkata".

string—
zones

Up to 3 more IANA zones to show the same moment in.

string[][]
assume24h

Treat bare hours like "9" as 24-hour time instead of asking AM or PM.

booleanfalse
value

Controlled text.

string—
defaultValue

Initial text when uncontrolled.

string""
onValueChange

Called with the field's text on every change.

(value: string) => void—
onTimeChange

Called with "HH:mm" and the UTC instant whenever the moment changes (also on mount and when `date` or `timeZone` change), or `null` while it's ambiguous.

(time: { time: string; instant: string } | null) => void—
pickMessage

Warning shown until AM or PM is picked.

string"Pick AM or PM so nobody shows up 12 hours off."
invalidMessage

Warning shown when the text isn't a time.

string"Try a time like 9:30 am, 21:30 or noon."
describe

Spells a time out, e.g. "9:00 PM, Tue night". Override to translate.

(hour: number, minute: number, weekday: string) => stringdescribeTime
skippedText

`{time}` is the typed time and `{saved}` the real one after the clocks jump.

string"{time} doesn't exist that day: the clocks spring forward…
repeatedText

`{time}` is the time that happens twice; `{which}` is `firstLabel` or `secondLabel`.

string"{time} happens twice that day as the clocks fall back. U…
firstLabel

Label of the first occurrence of a repeated time.

string"first"
secondLabel

Label of the second occurrence of a repeated time.

string"second"
useLabel

Label of the fix that switches to the other occurrence or the real time; `{time}` is filled in.

string"Use {time}"
nextDayLabel

Flag for a time on the next day in another zone.

string"next day"
previousDayLabel

Flag for a time on the previous day in another zone.

string"previous day"
meridiemQuestion

Accessible name of the AM/PM choice; `time` is like "7:30".

(time: string) => string(time) => `${time} in the morning or evening?`

* required. Also accepts the native props of <input> (className, aria-*, data-*…), passed through to it.

Accessibility

  • Visible <label>; aria-invalid and hint via aria-describedby
  • AM/PM choice is a roving-tabindex radiogroup with arrow keys
  • Hint in a polite live region
  • Animations respect reduced motion

Only what the source does. Test with your own screen reader and content before shipping.

Limitations

  • Needs a date and IANA time zone for DST checks
  • Parses common English time formats only
  • assume24h changes how bare hours are read

What it stops

Meeting booked 12 hours off

Also known as

time picker · time input · am pm · meeting time · dst · time zone · booking · appointment

Field type

Dependencies

Pairs well with