Skip to main content

TimePicker

  • Component overview: Time selection component for picking hours, minutes, and optional seconds. API follows antd TimePicker conventions (value / defaultValue / onChange / hourStep / minuteStep / disabledHours / showSecond).
  • Size baseline: Trigger 32px tall, panel column 106px wide, column max-height 332px, item height 38px, border radius 5px.
  • Implementation note: Trigger reuses the DatePicker visual rules. Panel is a multi-column scrollable list rendered inside an unstyledVisual Popover. Selecting an item commits the change immediately and keeps the panel open so users can adjust other columns.
  • TimePicker panel Figma spec

Basic Usage

Click the trigger to open the panel. Clicking on any column commits a partial change immediately (you can keep adjusting other columns).

Result
Loading...
Live Editor
const Demo = () => {
  const [time, setTime] = useState(undefined)
  return (
    <div style={{ padding: '40px 0' }}>
      <TimePicker
        className="w-62"
        value={time}
        onChange={setTime}
        placeholder="Select time"
      />
    </div>
  )
}
render(<Demo />)

States

Disabled

Result
Loading...
Live Editor
const Demo = () => {
  return (
    <div style={{ display: 'flex', gap: 16, padding: '40px 0' }}>
      <TimePicker className="w-62" disabled placeholder="Disabled empty" />
      <TimePicker className="w-62" disabled value={new Date(2026, 3, 20, 10, 30)} />
    </div>
  )
}
render(<Demo />)

With Value (Clearable)

Hover the trigger to reveal the clear button.

Result
Loading...
Live Editor
const Demo = () => {
  const [time, setTime] = useState(new Date(2026, 3, 20, 13, 30))
  return (
    <div style={{ padding: '40px 0' }}>
      <TimePicker
        className="w-62"
        value={time}
        onChange={setTime}
      />
    </div>
  )
}
render(<Demo />)

Steps

minuteStep={5} matches the Figma reference: minutes are listed as 00, 05, 1055.

Result
Loading...
Live Editor
const Demo = () => {
  const [time, setTime] = useState(new Date(2026, 3, 20, 13, 0))
  return (
    <div style={{ padding: '40px 0' }}>
      <TimePicker
        className="w-62"
        value={time}
        onChange={setTime}
        minuteStep={5}
      />
    </div>
  )
}
render(<Demo />)

With Seconds

Set showSecond to render the third column.

Result
Loading...
Live Editor
const Demo = () => {
  const [time, setTime] = useState(new Date(2026, 3, 20, 13, 30, 15))
  return (
    <div style={{ padding: '40px 0' }}>
      <TimePicker
        className="w-72"
        value={time}
        onChange={setTime}
        showSecond
      />
    </div>
  )
}
render(<Demo />)

Disabled Hours

Disable a range of hours via disabledHours. The corresponding items become non-interactive in the panel.

Result
Loading...
Live Editor
const Demo = () => {
  const [time, setTime] = useState(undefined)
  return (
    <div style={{ padding: '40px 0' }}>
      <TimePicker
        className="w-62"
        value={time}
        onChange={setTime}
        disabledHours={() => [0, 1, 2, 3, 4, 5, 6, 22, 23]}
        placeholder="Working hours only"
      />
    </div>
  )
}
render(<Demo />)

Custom Format

Result
Loading...
Live Editor
const Demo = () => {
  const [time, setTime] = useState(new Date(2026, 3, 20, 13, 5))
  const format = (d) => {
    const hh = d.getHours()
    const mm = String(d.getMinutes()).padStart(2, '0')
    const suffix = hh >= 12 ? 'PM' : 'AM'
    const h12 = ((hh + 11) % 12) + 1
    return `${h12}:${mm} ${suffix}`
  }
  return (
    <div style={{ padding: '40px 0' }}>
      <TimePicker
        className="w-62"
        value={time}
        onChange={setTime}
        formatTime={format}
      />
    </div>
  )
}
render(<Demo />)

Token Table

ElementPropertyToken / Value
TriggerBorder (default)Separators/Emphasized#CCCCCC
TriggerBorder (active)Grays/Black#000000
TriggerBackgroundBackgrounds/Primary#FFFFFF
Trigger (disabled)BackgroundGrays/Gray-1#EBEBEB
Value textColorLabels/Primary#000000
PlaceholderColorLabels/Tertiary#757575
Item textColorLabels/Secondary#3D3D3D
Item hoverBackgroundGrays/Gray-1#EBEBEB
PanelBackgroundGrays/White#FFFFFF
PanelShadowEffects/Shadow/Defaultrgba(0,0,0,0.1)
Check iconColorLabels/Primary#000000

Size Spec

DimensionValue
Trigger height32px
Trigger horizontal paddingSpacing_16 / Spacing_12
Trigger border radiusRadius_5 (5px)
Trigger fontBody/Regular (14px)
Panel column width106px
Panel column max-height332px (scrollable)
Item paddingSpacing_8 / Spacing_16
Item height38px
Check column20×20px reserved (no layout shift between selected / unselected)
Popover offset4px

Props

PropTypeDefaultDescription
valueDateControlled time value
defaultValueDateUncontrolled initial value
placeholderstring'Select time'Trigger placeholder text
formatTime(date: Date) => stringHH:mm (or HH:mm:ss when showSecond)Display formatter
hourStepnumber1Hour column step
minuteStepnumber1Minute column step
secondStepnumber1Second column step
showSecondbooleanfalseRender the second column
disabledHours() => number[]Disabled hour values
disabledMinutes(hour: number) => number[]Disabled minute values (per selected hour)
disabledSeconds(hour: number, minute: number) => number[]Disabled second values
disabledbooleanfalseDisable the picker
allowClearbooleantrueShow clear button on hover
openbooleanControlled open state
defaultOpenbooleanfalseInitial open state
onOpenChange(open: boolean) => voidOpen state change callback
onChange(date: Date | undefined) => voidValue change callback
classNamestringTrigger custom class

Differences from antd

  • value / onChange use native Date instead of dayjs.
  • Selecting any column commits the change immediately; the panel stays open so users can keep tweaking other columns.
  • use12Hours, showNow, and the range picker variant are not implemented yet.

Changelog

2026-05-29 — Initial release

New component: TimePicker (hours + minutes, optional seconds). Supports hourStep / minuteStep / secondStep, disabledHours / disabledMinutes / disabledSeconds, controlled / uncontrolled value, clearable trigger, controlled open mode, and custom time formatting.