Shift Time Input

Reads shift times the way they're meant, so 22:00–06:00 is 8 h overnight and 9–5 isn't logged as 20 h.

StopsNight shift logged as 20 hours

Installation

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

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

Usage

shift-time-input-usage.tsx
import { ShiftTimeInput, type ShiftValue } from "@/components/ui/shift-time-input"

<ShiftTimeInput label="Monday's shift" value={shift} onValueChange={setShift} />

What it catches

  • Overnight shifts ending the next day
  • Ambiguous 1–12 times with no AM/PM (9–5, 10–6)
  • Zero-length, over-long (maxMinutes) and 26:00-style entries
  • A break typed in hours instead of minutes
  • Real hours when clocks change mid-shift, given date and time zone

Props

ShiftTimeInput props
PropTypeDefault
label

Group label.

string"Shift"
value

Controlled value.

ShiftValue—
defaultValue

Initial value when uncontrolled.

ShiftValue{ start: "", end: "", breakMinutes: 0 }
onValueChange

Called on every change.

(value: ShiftValue) => void—
onShiftChange

Called with the worked-out shift, or null while a time can't be read.

(shift: ShiftSummary | null) => void—
maxMinutes

Longer shifts are questioned. Default 12 h.

number720
breakRule

Minimum break for long shifts; null turns it off. Default 30 min after 5 h.

BreakRule | null{ afterMinutes: 300, minBreakMinutes: 30 }
hour12

Read times back as 9:00 PM (true) or 21:00.

booleantrue
startLabel

Label of the start-time input.

string"Start"
endLabel

Label of the end-time input.

string"End"
breakLabel

Label of the break input.

string"Break (min)"
nextDayLabel

Shown after the length when the shift crosses midnight.

string"ends next day"
date

Day the shift starts, as YYYY-MM-DD. With `timeZone`, lengths count the real hours across a DST change.

string—
timeZone

IANA zone the shift is worked in, e.g. "Europe/London".

string—
dstMessages

Override the DST note (see `SHIFT_DST_MESSAGES`).

Partial<typeof SHIFT_DST_MESSAGES>—

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

Accessibility

  • Start, end and break inputs have <label>s; aria-invalid on unparseable times
  • Readback and status linked via aria-describedby and a polite live region
  • Errors use role=alert; warnings are text with icons
  • Length has an aria-label in words

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

Limitations

  • Break rules and maximums are what you configure; labour law is not built in
  • DST correction needs date and timeZone props

What it stops

Night shift logged as 20 hours

Also known as

shift · roster · timesheet · attendance · night shift · clock in clock out · rota · hours worked

Field type

Dependencies

Pairs well with