time-input

registry:ui

A 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

npx shadcn@latest add https://time-input.kaylee.jp/r/time-input.json

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

Default (24h)
12-hour
With seconds
12h + seconds
Custom placeholder
Autofill minutes
Round nearest
Round floor
Round ceiling
Avoid day rollover
Overflow hours (max 27)
Allow 24:00 only
Disabled

Localization

en-US
zh-CN
ja-JP
ko-KR
ar-SA
periodLabels

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

Off (default)
On

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

sm
default
lg

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
TabMove to the next segment
Shift+TabMove to the previous segment
Backspace on emptyJump focus back to previous segment
Space / ↑ / ↓ on AM/PMToggle between AM and PM

Props

PropTypeDefault
valuestring
defaultValuestring
onChange(value: string) => void
format"12h" | "24h""24h"
showSecondsbooleanfalse
placeholderstring
autoFillMinutesOnBlurbooleanfalse
roundMinutesToNearestnumber
roundMinutesMode"floor" | "ceil" | "nearest""nearest"
roundLastIntervalDownbooleanfalse
allowOverflowHoursbooleanfalse
maxOverflowHoursnumber99
localestring
periodLabels{ am: string; pm: string }
scrollToStepbooleanfalse
disabledbooleanfalse
size"sm" | "default" | "lg""default"
namestring
classNamestring

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.