react ~10 KB 0 deps v0.1.0 β†— GitHub β†—

@mshafiqyajid/react-time-picker

Headless time picker hook and styled component. Text input with dropdown columns for hours, minutes, and optional seconds. 12h and 24h formats, AM/PM toggle, minute step, min/max bounds, full keyboard navigation, ARIA listbox, portal to document.body. Zero dependencies.

Playground #

Props
TSX
import { TimePickerStyled } from "@mshafiqyajid/react-time-picker/styled";
import "@mshafiqyajid/react-time-picker/styles.css";

<TimePickerStyled
  value={value}
  onChange={setValue}
/>

Install #

npm install @mshafiqyajid/react-time-picker

Quick start #

import { TimePickerStyled } from "@mshafiqyajid/react-time-picker/styled";
import "@mshafiqyajid/react-time-picker/styles.css";

const [time, setTime] = useState("09:00");
<TimePickerStyled value={time} onChange={setTime} label="Meeting time" />

// 12h format with seconds
<TimePickerStyled
  value={time}
  onChange={setTime}
  format="12h"
  showSeconds
  step={15}
  label="Alarm time"
/>

Inline mode #

Set inline to always show the column picker without a text input or floating dropdown. Useful for embedding in forms or panels.

<TimePickerStyled
  value={time}
  onChange={setTime}
  inline
  label="Pick a time"
/>

Clearable #

Set clearable to show a Γ— button when a value is set. Clicking it calls onChange("").

<TimePickerStyled
  value={time}
  onChange={setTime}
  clearable
  label="Meeting time"
/>

Headless #

import { useTimePicker } from "@mshafiqyajid/react-time-picker";

const {
  inputProps,
  hourProps,
  minuteProps,
  periodProps,
  hours,
  minutes,
  period,
  isOpen,
  open,
  close,
  clear,
} = useTimePicker({
  defaultValue: "14:30",
  format: "24h",
  clearable: true,
  onChange: (v) => console.log(v),
});

return (
  <div>
    <input {...inputProps} placeholder="HH:mm" />
    {isOpen && (
      <div>
        {/* custom column UI */}
        <button onClick={() => hourProps.onChange(10)}>10</button>
        <button onClick={() => minuteProps.onChange(30)}>30</button>
      </div>
    )}
  </div>
);

Min / max bounds #

Pass min and max as "HH:mm" strings to restrict selectable times. Values outside the range are rejected in both the dropdown columns and keyboard arrow-key increments.

{/* Business hours only: 09:00–17:00 */}
<TimePickerStyled
  value={time}
  onChange={setTime}
  min="09:00"
  max="17:00"
  label="Appointment time"
/>

API #

PropTypeDefaultDescription
value / defaultValuestringβ€”Controlled or uncontrolled time value. "HH:mm", "HH:mm:ss", or "hh:mm AM"
onChange(v: string) => voidβ€”Called on every change. Always emits 24h canonical "HH:mm" or "HH:mm:ss"
format"12h" | "24h""24h"Display format. Input accepts both; output is always 24h canonical
showSecondsbooleanfalseShow seconds column in dropdown and include seconds in output
stepnumber1Minute step for the dropdown column (1, 5, 15, 30 are common)
min / maxstringβ€”Minimum / maximum time "HH:mm". Values outside bounds are rejected
size"sm" | "md" | "lg""md"Input height: 32 / 40 / 48 px
tone"neutral" | "primary" | "success" | "danger""neutral"Focus ring color
disabled / readOnly / required / invalidbooleanfalseForm-control states
label / hint / errorstringβ€”Field label, hint text, and error message. error sets tone="danger" automatically
id / name / placeholderstringβ€”Native input attributes
className / stylestring / CSSPropertiesβ€”Applied to the root wrapper
refRef<HTMLInputElement>β€”Forwarded to the primary input element
clearablebooleanfalseShow a Γ— clear button inside the input when a value is set. Calls onChange("")
inlinebooleanfalseAlways render the column picker without a floating dropdown or text input trigger
prefixReactNodeβ€”Decorative node rendered before the input text (e.g. a clock icon)
suffixReactNodeβ€”Decorative node rendered after the input text (e.g. a timezone label)
onFocus() => voidβ€”Called when the input gains focus
onBlur() => voidβ€”Called when the input loses focus
localestring"en-US"BCP 47 locale tag used to localise AM/PM labels in 12h mode (e.g. "fr-FR", "de-DE")

Keyboard #

KeyAction
Click / FocusOpens dropdown
ArrowDown (on input, closed)Opens dropdown
ArrowUp / ArrowDown (on input, focused)Increments / decrements the segment under the cursor (hours, minutes, seconds, or period)
EscapeCloses dropdown
TabCloses dropdown and moves focus
ArrowUp / ArrowDown (in column)Navigate options
Enter / Space (on option)Selects the option
Click outsideCloses dropdown

CSS variables #

:root {
  --rtp-bg: #ffffff;
  --rtp-border: #e4e4e7;
  --rtp-border-hover: #a1a1aa;
  --rtp-border-focus: #6366f1;
  --rtp-fg: #18181b;
  --rtp-label-fg: #3f3f46;
  --rtp-hint-fg: #71717a;
  --rtp-icon-fg: #71717a;
  --rtp-placeholder: #a1a1aa;
  --rtp-sep-fg: #a1a1aa;
  --rtp-dropdown-bg: #ffffff;
  --rtp-dropdown-border: #e4e4e7;
  --rtp-dropdown-shadow: 0 4px 16px rgba(0, 0, 0, 0.10);
  --rtp-option-active-bg: #eef2ff;
  --rtp-option-active-fg: #4338ca;
  --rtp-option-hover-bg: #f4f4f5;
  --rtp-height-sm: 2rem;
  --rtp-height-md: 2.5rem;
  --rtp-height-lg: 3rem;
  --rtp-font-size-sm: 0.8125rem;
  --rtp-font-size-md: 0.875rem;
  --rtp-font-size-lg: 1rem;
  --rtp-radius-sm: 6px;
  --rtp-radius-md: 8px;
  --rtp-radius-lg: 10px;
  --rtp-duration: 150ms;
  --rtp-ease: cubic-bezier(0.4, 0, 0.2, 1);
}
Edit this page on GitHub