TimeInput

free

TimeInput 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 TimeInput

Examples

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
:
:
Local
import 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

PropDescriptionTypeDefault
amLabelText accepted and displayed for AM in 12-hour mode.string'am'
amPmPlaceholderPlaceholder for the AM/PM field.string'am'
clearableShows a clear button after a value exists.booleantrue
defaultValueInitial uncontrolled value.TimeInputValue-
disabledDisables every time segment and wrapper interaction.booleanfalse
embeddedRenders only the inner time fields, without InputWrapper.booleanfalse
formatSelects 12-hour or 24-hour fields.TimeInputFormat'24'
idId used for the first time field.stringgenerated
invalidApplies invalid input styling.boolean-
nameName applied to the hours field.string-
nextRefField to focus after the final time segment.RefObject<HTMLInputElement | null>-
onChangeCalled with the next date value, or null after clear.TimeInputChangeHandler-
pmLabelText accepted and displayed for PM in 12-hour mode.string'pm'
prefixPrefix content rendered inside the input wrapper.string | ReactNode-
refRef forwarded to the first time field.Ref<HTMLInputElement>-
showSecondsAdds a seconds field after minutes.booleanfalse
sizeInput wrapper size.ControlSize'md'
suffixSuffix content rendered inside the input wrapper.string | ReactNodeclock icon
timeFieldClassClass names applied to each time segment field.string-
timeFieldPlaceholderPlaceholder for hour, minute, and second fields.string'--'
valueControlled value.TimeInputValue-
...nativePropsNative props for the wrapper span.ComponentPropsWithRef<'span'>-

TimeInput.TimeInputRange

PropDescriptionTypeDefault
amPmPlaceholderPlaceholder for each AM/PM field.string'am'
clearableShows a clear button for the range.booleanfalse
defaultValueInitial uncontrolled start and end values.TimeInputRangeValue[null, null]
disabledDisables both time inputs.booleanfalse
formatSelects 12-hour or 24-hour fields.TimeInputFormat'24'
idId used for the first range field.stringgenerated
invalidApplies invalid input styling.boolean-
nameName applied to the first range field.string-
onChangeCalled with the next [start, end] range.TimeInputRangeChangeHandler-
prefixPrefix content rendered inside the range wrapper.string | ReactNode-
refRef forwarded to the first time field.Ref<HTMLInputElement>-
seperatorText rendered between the start and end fields.string'~'
showSecondsAdds seconds fields to both start and end inputs.booleanfalse
sizeInput wrapper size.ControlSize'md'
suffixSuffix content rendered inside the range wrapper.string | ReactNodeclock icon
timeFieldClassClass names applied to each time segment field.string-
timeFieldPlaceholderPlaceholder for hour, minute, and second fields.string'--'
valueControlled range value.TimeInputRangeValue-
...nativePropsNative 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;