Skip to main content
View Zag.js on Github
Join the Discord server

Time Picker

The time picker is used to time picker a time value, independently from a date value.

This component builds on top of the native <input type=time> experience and provides a more customizable and consistent user experience.

Properties

Features

  • Select a time value in the menu...
  • ...or type it in the input.
  • Use seconds for more precision.
  • Use different steps for each unit.
  • Set a minimum and maximum value.
  • Clear button to reset the value.
  • Navigate in the menu with keyboard.
  • Support for different time formats.

Installation

To use the Time Picker machine in your project, run the following command in your command line:

npm install @zag-js/time-picker @zag-js/react # or yarn add @zag-js/time-picker @zag-js/react

Anatomy

To set up the Time Picker correctly, you'll need to understand its anatomy and how we name its parts.

Each part includes a data-part attribute to help identify them in the DOM.

Usage

First, import the time picker package into your project

import * as timePicker from "@zag-js/time-picker"

The Time Picker package exports two key functions:

  • machine — The state machine logic for the Time Picker widget.
  • connect — The function that translates the machine's state to JSX attributes and event handlers.

You'll also need to provide a unique id to the useMachine hook. This is used to ensure that every part has a unique identifier.

Next, import the required hooks and functions for your framework and use the Time Picker machine in your project 🔥

import * as timePicker from "@zag-js/time-picker" import { useMachine, normalizeProps, Portal } from "@zag-js/react" import { useId } from "react" export function TimePicker() { const service = useMachine(timePicker.machine, { id: useId() }) const api = timePicker.connect(service, normalizeProps) return ( <> <div {...api.getRootProps()}> <div {...api.getControlProps()} style={{ display: "flex", gap: "10px" }} > <input {...api.getInputProps()} /> <button {...api.getTriggerProps()}>🗓</button> <button {...api.getClearTriggerProps()}></button> </div> <Portal> <div {...api.getPositionerProps()}> <div {...api.getContentProps()}> <div {...api.getColumnProps({ unit: "hour" })}> {api.getHours().map((item) => ( <button key={item.value} {...api.getHourCellProps({ value: item.value })} > {item.label} </button> ))} </div> <div {...api.getColumnProps({ unit: "minute" })}> {api.getMinutes().map((item) => ( <button key={item.value} {...api.getMinuteCellProps({ value: item.value })} > {item.label} </button> ))} </div> <div {...api.getColumnProps({ unit: "second" })}> {api.getSeconds().map((item) => ( <button key={item.value} {...api.getSecondCellProps({ value: item.value })} > {item.label} </button> ))} </div> <div {...api.getColumnProps({ unit: "period" })}> <button {...api.getPeriodCellProps({ value: "am" })}>AM</button> <button {...api.getPeriodCellProps({ value: "pm" })}>PM</button> </div> </div> </div> </Portal> </div> </> ) }

Setting the initial value

To set the initial value of the time picker, pass the value property to the time picker machine's context.

The value property must be an instance of Time exported from @internationalized/date, or undefined.

const service = useMachine(timePicker.machine, { id: useId(), defaultValue: new Time(12, 30), })

Disabling the time picker

To disable the time picker, set the disabled property in the machine's context to true.

const service = useMachine(timePicker.machine, { id: useId(), disabled: true, })

Usage with seconds

By default, the time picker only shows the hour and minute cells. To show the second cell, set the showSeconds property in the machine's context to true.

const service = useMachine(timePicker.machine, { id: useId(), withSeconds: true, })

Setting the locale

To set the locale of the time picker, pass the locale property to the time picker machine's context.

This will affect the presence of the period cell and the time format.

const service = useMachine(timePicker.machine, { id: useId(), locale: "fr-FR", })

Setting steps

To set the steps for the time picker, pass the steps property to the time picker machine's context.

The steps property must be an object with the following properties:

  • hour — The step for the hour cell.
  • minute — The step for the minute cell.
  • second — The step for the second cell.
const service = useMachine(timePicker.machine, { id: useId(), steps: { hour: 2, minute: 15, second: 30, }, })

Setting min and max values

To set the minimum and maximum values for the time picker, pass the min and max properties to the time picker machine's context.

The min and max properties must be an instance of Time exported from @internationalized/date.

const service = useMachine(timePicker.machine, { id: useId(), min: new Time(10), // Only allow times after 10:00:00 max: new Time(18, 30, 20), // Only allow times before 18:30:20 })

Listening for focus changes

When an item is focused with the keyboard, use the onFocusChange to listen for the change and do something with it.

const service = useMachine(timePicker.machine, { id: useId(), onFocusChange(details) { // details => { focusedCell: { value: number, unit: TimeUnit } } console.log(details) }, })

Listening for value changes

When the value changes, use the onValueChange property to listen for the change and do something with it.

const service = useMachine(timePicker.machine, { id: useId(), onValueChange(details) { // details => { value?: Time, valueAsString?: string } console.log(details) }, })

Listening for open and close events

When the time picker is opened or closed, the onOpenChange callback is called. You can listen for these events and do something with it.

const service = useMachine(timePicker.machine, { id: useId(), onOpenChange(details) { // details => { open: boolean } console.log("time picker opened") }, })

Usage within dialog

When using the time picker within a dialog, you'll need to avoid rendering the time picker in a Portal or Teleport. This is because the dialog will trap focus within it, and the time picker will be rendered outside the dialog.

Consider designing a portalled property in your component to allow you decide where to render the time picker in a portal.

Styling guide

Earlier, we mentioned that each time picker part has a data-part attribute added to them to time picker and style them in the DOM.

Open and closed state

When the time picker is open, the trigger and content is given a data-state attribute.

[data-part="trigger"][data-state="open|closed"] { /* styles for open or closed state */ } [data-part="content"][data-state="open|closed"] { /* styles for open or closed state */ }

Cell state

Items are given a data-state attribute, indicating whether they are selected.

[data-part="hour|minute|second|period-cell"][data-selected] { /* styles for selected or unselected state */ }

Disabled state

When the time picker is disabled, the trigger and label is given a data-disabled attribute.

[data-part="trigger"][data-disabled] { /* styles for disabled time picker state */ } [data-part="label"][data-disabled] { /* styles for disabled label state */ }

Optionally, when an item is disabled, it is given a data-disabled attribute.

Methods and Properties

Machine Context

The time picker machine exposes the following context properties:

  • localestringThe locale (BCP 47 language tag) to use when formatting the time.
  • valueTimeThe controlled selected time.
  • defaultValueTimeThe initial selected time when rendered. Use when you don't need to control the selected time.
  • openbooleanWhether the timepicker is open
  • defaultOpenbooleanWhether the timepicker open state is controlled by the user
  • idsPartial<{ trigger: string; input: string; positioner: string; content: string; clearTrigger: string; control: string; column(unit: TimeUnit): string; }>The ids of the elements in the date picker. Useful for composition.
  • namestringThe `name` attribute of the input element.
  • positioningPositioningOptionsThe user provided options used to position the time picker content
  • placeholderstringThe placeholder text of the input.
  • disabledbooleanWhether the time picker is disabled.
  • readOnlybooleanWhether the time picker is read-only.
  • minTimeThe minimum time that can be selected.
  • maxTimeThe maximum time that can be selected.
  • steps{ hour?: number; minute?: number; second?: number; }The steps of each time unit.
  • allowSecondsbooleanWhether to show the seconds.
  • onValueChange(value: ValueChangeDetails) => voidFunction called when the value changes.
  • onOpenChange(details: OpenChangeDetails) => voidFunction called when the time picker opens or closes.
  • onFocusChange(details: FocusChangeDetails) => voidFunction called when the focused date changes.
  • disableLayerbooleanWhether to disable the interaction outside logic
  • dir"ltr" | "rtl"The document's text/writing direction.
  • idstringThe unique identifier of the machine.
  • getRootNode() => ShadowRoot | Node | DocumentA root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.

Machine API

The time picker api exposes the following methods:

  • focusedbooleanWhether the input is focused
  • openbooleanWhether the time picker is open
  • valueTimeThe selected time
  • valueAsStringstringThe selected time as a string
  • hour12booleanWhether the time picker is in 12-hour format (based on the locale prop)
  • reposition(options?: PositioningOptions) => voidFunction to reposition the time picker content
  • setOpen(nextOpen: boolean) => voidFunction to open the time picker
  • clearValue() => voidFunction to clear the selected time
  • setValue(value: string | Time) => voidFunction to set the selected time
  • setUnitValue{ (unit: "period", value: TimeUnit, value: number): void; }Function to set the focused time unit
  • getHours() => Cell[]Get the available hours that will be displayed in the time picker
  • getMinutes() => Cell[]Get the available minutes that will be displayed in the time picker
  • getSeconds() => Cell[]Get the available seconds that will be displayed in the time picker

Data Attributes

Root
data-scope
time-picker
data-part
root
data-state
"open" | "closed"
data-disabled
Present when disabled
data-readonly
Present when read-only
Label
data-scope
time-picker
data-part
label
data-state
"open" | "closed"
data-disabled
Present when disabled
data-readonly
Present when read-only
Control
data-scope
time-picker
data-part
control
data-disabled
Present when disabled
Trigger
data-scope
time-picker
data-part
trigger
data-placement
The placement of the trigger
data-readonly
Present when read-only
data-state
"open" | "closed"
ClearTrigger
data-scope
time-picker
data-part
clear-trigger
data-readonly
Present when read-only
Content
data-scope
time-picker
data-part
content
data-state
"open" | "closed"
data-placement
The placement of the content
Column
data-scope
time-picker
data-part
column
data-focus
Present when focused
HourCell
data-scope
time-picker
data-part
hour-cell
data-disabled
Present when disabled
data-selected
Present when selected
data-focus
Present when focused
data-value
The value of the item
MinuteCell
data-scope
time-picker
data-part
minute-cell
data-disabled
Present when disabled
data-selected
Present when selected
data-value
The value of the item
data-focus
Present when focused
SecondCell
data-scope
time-picker
data-part
second-cell
data-disabled
Present when disabled
data-selected
Present when selected
data-value
The value of the item
data-focus
Present when focused
PeriodCell
data-scope
time-picker
data-part
period-cell
data-selected
Present when selected
data-focus
Present when focused
data-value
The value of the item

Accessibility

Adheres to the ListBox WAI-ARIA design pattern.

Keyboard Interactions

  • ArrowLeft
    Moves focus to the previous column.
  • ArrowRight
    Moves focus to the next column.
  • ArrowUp
    Moves focus to the previous cell within the current column.
  • ArrowDown
    Moves focus to the next cell within the current column.
  • Enter
    Selects the focused hour/minute/second/period and moves focus to the next column.
  • Esc
    Closes the time picker without selecting any time.

Edit this page on GitHub

Proudly made in🇳🇬by Segun Adebayo

Copyright © 2025
On this page