Components
Time Range Picker
Range picker with integrated segmented start/end field, dual time spinners, and optional confirm flow using plain Date tuples.
Time Range Picker combines a segmented start/end time field and clock icon in one bordered shell, with dual column spinners in a popover. Values are a plain [Date, Date] | null tuple — only clock portions are read or written.
Interactions
| Event | Behavior |
|---|---|
| Focus the start or end field | Does not open the popover (open via clock trigger). |
| Click the clock icon | Toggles the popover open or closed. |
| Select or type a time | Updates the start or end value. |
| Escape or click outside | Closes the popover. |
Design guide
- Range over two pickers — Use one control for start/end so ordering and validation stay in sync.
- Format drives columns — Same
format/use12Hoursrules as Time Picker; both spinners share the template. - Order enforcement —
order(defaulttrue) keeps the end time at or after the start when either side changes.
Installing
pnpm dlx shadcn@latest add https://ui.isaacfei.com/r/time-range-picker.jsonUsage
import { useState } from "react";
import type { TimeRangeTuple } from "@/components/f-ui/date-internals/use-segmented-time-range-field";
import { TimeRangePicker } from "@/components/f-ui/time-range-picker/time-range-picker";
export function Example() {
const [range, setRange] = useState<TimeRangeTuple | null>(null);
return (
<TimeRangePicker
label="Hours"
value={range}
onChange={setRange}
/>
);
}Examples
Start and End
900AM
500PM
Segmented start/end field with dual spinners in the popover.
"use client";
import { useState } from "react";
import type { TimeRangeTuple } from "@/components/f-ui/date-internals/use-segmented-time-range-field";
import { TimeRangePicker } from "@/components/f-ui/time-range-picker/time-range-picker";
export function TimeRangePickerDemo() {
const [range, setRange] = useState<TimeRangeTuple | null>(() => [
new Date(2026, 4, 21, 9, 0, 0, 0),
new Date(2026, 4, 21, 17, 0, 0, 0),
]);
return (
<div className="max-w-md w-full">
<TimeRangePicker
label="Office hours"
value={range}
onChange={setRange}
description="Segmented start/end field with dual spinners in the popover."
order
/>
</div>
);
}API Reference
| Prop | Type | Default |
|---|---|---|
value | [Date, Date] | null | — |
onChange | (range: [Date, Date] | null) => void | — |
format | string | HH:mm or hh:mm a |
use12Hours | boolean | false |
step | { hour?, minute?, second? } | 1 per unit |
disabledTime | (current: Date) => { hours?, minutes?, seconds? } | — |
order | boolean | true |
changeOnScroll | boolean | false |
disabled | boolean | false |
isInvalid | boolean | false |
label | ReactNode | — |
description / errorMessage | ReactNode | — |
locale | string | i18n provider |
classNames | { root?, field?, shell?, … } | — |
Each Date in the tuple uses only its clock portion (normalized to today in the local timezone).