Skip to main content
0.50.0
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 [state, send] = useMachine(timePicker.machine({ id: useId() }), { context: controls.context, }) const api = timePicker.connect(state, send, normalizeProps) return ( <> <div {...api.rootProps}> <div {...api.controlProps} style={{ display: "flex", gap: "10px" }}> <input {...api.inputProps} /> <button {...api.triggerProps}>🗓</button> <button {...api.clearTriggerProps}></button> </div> <Portal> <div {...api.positionerProps}> <div {...api.contentProps}> <div {...api.getContentColumnProps({ type: "hour" })}> {api.getAvailableHours().map((hour) => ( <button key={hour} {...api.getHourCellProps({ hour })}> {hour} </button> ))} </div> <div {...api.getContentColumnProps({ type: "minute" })}> {api.getAvailableMinutes().map((minute) => ( <button key={minute} {...api.getMinuteCellProps({ minute })}> {minute} </button> ))} </div> <div {...api.getContentColumnProps({ type: "second" })}> {api.getAvailableSeconds().map((second) => ( <button key={second} {...api.getSecondCellProps({ second })}> {second} </button> ))} </div> <div {...api.getContentColumnProps({ type: "period" })}> <button {...api.getPeriodCellProps({ period: "am" })}> AM </button> <button {...api.getPeriodCellProps({ period: "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 [state, send] = useMachine( timePicker.machine({ id: useId(), value: 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 [state, send] = 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 [state, send] = 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 [state, send] = 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 [state, send] = 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 [state, send] = 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 [state, send] = 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 [state, send] = 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 [state, send] = 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.
  • valueTime | undefinedThe selected time.
  • openbooleanWhether the timepicker is open
  • open.controlledbooleanWhether the datepicker open state is controlled by the user
  • idsElementIdsThe 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.
  • withSecondsbooleanWhether 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.

Machine API

The time picker api exposes the following methods:

  • isFocusedbooleanWhether the input is focused
  • isOpenbooleanWhether the time picker is open
  • valueTime | undefinedThe selected time
  • valueAsStringstring | undefinedThe selected time as a string
  • is12HourFormatbooleanWhether the time picker is in 12-hour format (based on the locale prop)
  • reposition(options?: PositioningOptions) => voidFunction to reposition the time picker content
  • open() => voidFunction to open the time picker
  • close() => voidFunction to close the time picker
  • clearValue() => voidFunction to clear the selected time
  • getAvailableHours() => string[]Get the available hours that will be displayed in the time picker
  • getAvailableMinutes() => string[]Get the available minutes that will be displayed in the time picker
  • getAvailableSeconds() => string[]Get the available seconds that will be displayed in the time picker

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

On this page