TimeInput
freeTimeInput captures single times or ranges with segmented keyboard-friendly fields.
dayjs
Preview
Dark
:
import TimeInput from '@/components/ui/TimeInput';
export default function UsageDemo() {
return (
<div className="w-full max-w-xs">
<TimeInput defaultValue={new Date(2026, 0, 1, 9, 30)} />
</div>
);
}Installation
Add this component with the NateUI CLI.
npx nateui@latest add TimeInputExamples
Basic
Preview
Dark
:
import TimeInput from '@/components/ui/TimeInput';
export default function BasicDemo() {
return (
<div className="w-full max-w-xs">
<TimeInput />
</div>
);
}Time Range Input
Preview
Dark
:
~ :
import dayjs from 'dayjs';
import TimeInput from '@/components/ui/TimeInput';
export default function TimeRangeInputDemo() {
const start = new Date(2026, 0, 1, 9, 0);
return (
<div className="w-full max-w-sm">
<TimeInput.TimeInputRange
clearable
defaultValue={[
start,
dayjs(start).add(60, 'minutes').toDate(),
]}
/>
</div>
);
}Controlled
Preview
Dark
:
:
~ :
Single: 09:00 AM / Range: 09:00 AM -10:00 AM
import { useState } from 'react';
import dayjs from 'dayjs';
import TimeInput from '@/components/ui/TimeInput';
const formatTime = (value: Date | null) =>
value
? value.toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit',
})
: '--:--';
export default function ControlledDemo() {
const start = new Date(2026, 0, 1, 9, 0);
const [timeValue, setTimeValue] = useState<Date | null>(start);
const [rangeValue, setRangeValue] = useState<[Date | null, Date | null]>([
start,
dayjs(start).add(60, 'minutes').toDate(),
]);
return (
<div className="flex w-full max-w-sm flex-col gap-4">
<TimeInput value={timeValue} onChange={setTimeValue} />
<TimeInput.TimeInputRange
value={rangeValue}
onChange={setRangeValue}
/>
<div className="rounded-control border border-border bg-muted px-3 py-2 text-xs text-muted-foreground">
Single: {formatTime(timeValue)} / Range: {formatTime(rangeValue[0])} -
{formatTime(rangeValue[1])}
</div>
</div>
);
}Seconds
Preview
Dark
: :
import TimeInput from '@/components/ui/TimeInput';
export default function SecondsDemo() {
return (
<div className="w-full max-w-xs">
<TimeInput
showSeconds
defaultValue={new Date(2026, 0, 1, 9, 30, 45)}
/>
</div>
);
}AM PM
Preview
Dark
:
import TimeInput from '@/components/ui/TimeInput';
export default function AmPmDemo() {
return (
<div className="w-full max-w-xs">
<TimeInput
format="12"
defaultValue={new Date(2026, 0, 1, 14, 30)}
/>
</div>
);
}Sizes
Preview
Dark
:
:
:
import TimeInput from '@/components/ui/TimeInput';
export default function SizesDemo() {
return (
<div className="flex w-full max-w-xs flex-col gap-4">
<TimeInput size="sm" />
<TimeInput />
<TimeInput size="lg" />
</div>
);
}Disabled
Preview
Dark
:
import TimeInput from '@/components/ui/TimeInput';
export default function DisabledDemo() {
return (
<div className="w-full max-w-xs">
<TimeInput disabled defaultValue={new Date(2026, 0, 1, 9, 30)} />
</div>
);
}Affix
Preview
Dark
UTC
:
:
Localimport TimeInput from '@/components/ui/TimeInput';
export default function AffixDemo() {
return (
<div className="flex w-full max-w-xs flex-col gap-4">
<TimeInput
prefix={
<span className="text-xs font-medium text-muted-foreground">
UTC
</span>
}
suffix={null}
/>
<TimeInput
suffix={
<span className="text-xs font-medium text-muted-foreground">
Local
</span>
}
/>
</div>
);
}Invalid
Preview
Dark
:
import TimeInput from '@/components/ui/TimeInput';
export default function InvalidDemo() {
return (
<div className="w-full max-w-xs">
<TimeInput invalid />
</div>
);
}API
TimeInput uses separate fields for hours, minutes, optional seconds, and optional AM/PM. Arrow keys increment or decrement the focused segment.
TimeInput
| Prop | Description | Type | Default |
|---|---|---|---|
amLabel | Text accepted and displayed for AM in 12-hour mode. | string | 'am' |
amPmPlaceholder | Placeholder for the AM/PM field. | string | 'am' |
clearable | Shows a clear button after a value exists. | boolean | true |
defaultValue | Initial uncontrolled value. | TimeInputValue | - |
disabled | Disables every time segment and wrapper interaction. | boolean | false |
embedded | Renders only the inner time fields, without InputWrapper. | boolean | false |
format | Selects 12-hour or 24-hour fields. | TimeInputFormat | '24' |
id | Id used for the first time field. | string | generated |
invalid | Applies invalid input styling. | boolean | - |
name | Name applied to the hours field. | string | - |
nextRef | Field to focus after the final time segment. | RefObject<HTMLInputElement | null> | - |
onChange | Called with the next date value, or null after clear. | TimeInputChangeHandler | - |
pmLabel | Text accepted and displayed for PM in 12-hour mode. | string | 'pm' |
prefix | Prefix content rendered inside the input wrapper. | string | ReactNode | - |
ref | Ref forwarded to the first time field. | Ref<HTMLInputElement> | - |
showSeconds | Adds a seconds field after minutes. | boolean | false |
size | Input wrapper size. | ControlSize | 'md' |
suffix | Suffix content rendered inside the input wrapper. | string | ReactNode | clock icon |
timeFieldClass | Class names applied to each time segment field. | string | - |
timeFieldPlaceholder | Placeholder for hour, minute, and second fields. | string | '--' |
value | Controlled value. | TimeInputValue | - |
...nativeProps | Native props for the wrapper span. | ComponentPropsWithRef<'span'> | - |
TimeInput.TimeInputRange
| Prop | Description | Type | Default |
|---|---|---|---|
amPmPlaceholder | Placeholder for each AM/PM field. | string | 'am' |
clearable | Shows a clear button for the range. | boolean | false |
defaultValue | Initial uncontrolled start and end values. | TimeInputRangeValue | [null, null] |
disabled | Disables both time inputs. | boolean | false |
format | Selects 12-hour or 24-hour fields. | TimeInputFormat | '24' |
id | Id used for the first range field. | string | generated |
invalid | Applies invalid input styling. | boolean | - |
name | Name applied to the first range field. | string | - |
onChange | Called with the next [start, end] range. | TimeInputRangeChangeHandler | - |
prefix | Prefix content rendered inside the range wrapper. | string | ReactNode | - |
ref | Ref forwarded to the first time field. | Ref<HTMLInputElement> | - |
seperator | Text rendered between the start and end fields. | string | '~' |
showSeconds | Adds seconds fields to both start and end inputs. | boolean | false |
size | Input wrapper size. | ControlSize | 'md' |
suffix | Suffix content rendered inside the range wrapper. | string | ReactNode | clock icon |
timeFieldClass | Class names applied to each time segment field. | string | - |
timeFieldPlaceholder | Placeholder for hour, minute, and second fields. | string | '--' |
value | Controlled range value. | TimeInputRangeValue | - |
...nativeProps | Native props for the wrapper span. | ComponentPropsWithRef<'span'> | - |
Types
type TimeInputValue = Date | null;
type TimeInputRangeValue = [Date | null, Date | null];
type TimeInputFormat = '12' | '24';
type ControlSize = 'sm' | 'md' | 'lg';
type TimeInputChangeHandler = (value: TimeInputValue) => void;
type TimeInputRangeChangeHandler = (value: TimeInputRangeValue) => void;