time-input
registry:uiA flexible time input for shadcn/ui projects. Supports 24h and 12h formats, AM/PM toggle, optional seconds, and auto zero-padding on blur — everything the native type="time"can't do.
Install
Requires an existing shadcn/ui project (npx shadcn@latest init). After installing, import from @/components/ui/time-input.
Live demo
24-hour
value: 14:05
12-hour
value: 14:30
With seconds
value: 09:45:30
Variants
Localization
Pass a BCP 47 locale string to derive labels via Intl.DateTimeFormat, or supply periodLabels directly when your i18n library already has the strings. Omitting both keeps AM / PM and avoids any SSR mismatch.
Scroll to step
With scrollToStep, click a segment to focus it, then scroll to change its value. Disabled by default so scroll never fires on hover and doesn't interfere with page scrolling.
Sizes
Usage
import { TimeInput } from "@/components/ui/time-input"
// Controlled (24h)
const [time, setTime] = React.useState("14:05")
<TimeInput value={time} onChange={setTime} />
// 12-hour with AM/PM
<TimeInput format="12h" value={time} onChange={setTime} />
// With seconds
<TimeInput showSeconds value={time} onChange={setTime} />
// Custom placeholder
<TimeInput placeholder="--" />
// Fill minutes with 00 when only hours are entered
<TimeInput autoFillMinutesOnBlur />
// Round to the nearest 5 minutes on blur
<TimeInput roundMinutesToNearest={5} />
// Round down or up instead
<TimeInput roundMinutesToNearest={5} roundMinutesMode="floor" />
<TimeInput roundMinutesToNearest={5} roundMinutesMode="ceil" />
// Avoid rolling past 24:00 at the end of the day
<TimeInput
roundMinutesToNearest={5}
roundLastIntervalDown
/>
// Allow business-hour overflow such as 27:00
<TimeInput allowOverflowHours maxOverflowHours={27} defaultValue="27:00" />
// Allow exactly 24:00, but nothing beyond
<TimeInput allowOverflowHours maxOverflowHours={24} defaultValue="24:00" />
// Localized AM/PM via Intl.DateTimeFormat
<TimeInput format="12h" locale="ko-KR" />
// Manual labels — use when your i18n library has the strings
<TimeInput format="12h" periodLabels={{ am: t("time.am"), pm: t("time.pm") }} />
// Scroll to step (click a segment first, then scroll)
<TimeInput scrollToStep />
// Sizes
<TimeInput size="sm" />
<TimeInput size="default" />
<TimeInput size="lg" />
// Disabled
<TimeInput disabled defaultValue="09:00" />
// Native form submission
<form action="/submit">
<TimeInput name="departure" defaultValue="09:00" />
</form>
// React Hook Form
<Controller
control={control}
name="startTime"
render={({ field }) => (
<TimeInput value={field.value} onChange={field.onChange} />
)}
/>Keyboard
| ↑ / ↓ | Increment or decrement the focused segment |
| Tab | Move to the next segment |
| Shift+Tab | Move to the previous segment |
| Backspace on empty | Jump focus back to previous segment |
| Space / ↑ / ↓ on AM/PM | Toggle between AM and PM |
Props
| Prop | Type | Default |
|---|---|---|
| value | string | — |
| defaultValue | string | — |
| onChange | (value: string) => void | — |
| format | "12h" | "24h" | "24h" |
| showSeconds | boolean | false |
| placeholder | string | — |
| autoFillMinutesOnBlur | boolean | false |
| roundMinutesToNearest | number | — |
| roundMinutesMode | "floor" | "ceil" | "nearest" | "nearest" |
| roundLastIntervalDown | boolean | false |
| allowOverflowHours | boolean | false |
| maxOverflowHours | number | 99 |
| locale | string | — |
| periodLabels | { am: string; pm: string } | — |
| scrollToStep | boolean | false |
| disabled | boolean | false |
| size | "sm" | "default" | "lg" | "default" |
| name | string | — |
| className | string | — |
The value / onChange pair always uses 24-hour format ("HH:mm" or "HH:mm:ss") regardless of the format prop. The component emits an empty string while either hours or minutes is unfilled.