TimePicker

A time of day on snapping wheels — hour, minute, optional second and AM/PM — in a bottom sheet or inline.

Open Source

Installation

TimePicker ships with the library — install it once, then import:

import { TimePicker } from "@delacour/react-native-ui/time-picker";

Or copy the source into your project, to own and edit it:

bunx delacour@latest add time-picker

It needs the sheet engine

time-picker imports BottomSheet, so installing it installs @delacour/react-native-bottom-sheet too — even if you only ever use presentation="inline". Mount BottomSheetProvider once, inside DelacourProvider, and rebuild the dev client.

Usage

In a sheet
app/index.tsx
import { TimePicker, type TimeValue } from "@/components/ui/time-picker";
import { useState } from "react";
import { View } from "react-native";

export default function Example() {
  const [time, setTime] = useState<TimeValue | null>(null);

  return (
    <View className="flex-1 justify-center p-4">
      <TimePicker value={time} onValueChange={setTime} />
    </View>
  );
}

The trigger is an Input's box, so it lines up with the fields around it. Pressing it opens a sheet of wheels. onValueChange fires each time a wheel comes to rest; Done closes the sheet, and swiping it away keeps the value.

The value is always 24-hour. { hour: 15, minute: 5 } is 3:05 PM whatever the clock shows; hourCycle and locale change how it reads, never what is stored. null means "not set" and shows the placeholder.

Minute step

minuteStep sets the minute wheel's granularity. A value off the step is rounded to it, not refused.

<TimePicker minuteStep={15} placeholder="Pick a slot" value={time} onValueChange={setTime} />

12 and 24 hours

12 and 24 hours
12 and 24 hours

hourCycle defaults to "auto", which follows the locale. Set 12 or 24 to pin it.

<TimePicker hourCycle={12} locale="en-US" value={time} onValueChange={setTime} />
<TimePicker hourCycle={24} locale="en-GB" value={time} onValueChange={setTime} />

Bounds

Opening hours

minTime and maxTime dim the rows outside them. A wheel that comes to rest on a dimmed row moves back to the nearest time that can be picked, so onValueChange never sees one.

<TimePicker
  minTime={{ hour: 9, minute: 0 }}
  maxTime={{ hour: 17, minute: 30 }}
  minuteStep={5}
  value={time}
  onValueChange={setTime}
/>

Inline

Inline

presentation="inline" draws the bare wheel in place — no trigger and no sheet.

<TimePicker presentation="inline" value={time} onValueChange={setTime} />

TimePickerWheel is the same wheel with no picker around it, for a screen or another control that composes one.

<TimePickerWheel minuteStep={5} value={time} onValueChange={setTime} />

Seconds

showSeconds adds a third wheel, and the value carries a second field.

<TimePicker hourCycle={24} showSeconds value={time} onValueChange={setTime} />

Controlled open

isOpen and onOpenChange drive the sheet from outside.

<TimePicker isOpen={open} onOpenChange={setOpen} value={time} onValueChange={setTime} />

In a Field

In a field

Inside a Field the picker takes the field's invalid and disabled state, reads Field.Label as its accessible name, and opens when the label is tapped. Write the parts out to change any of them.

<Field isInvalid={time === null}>
  <Field.Label>Reminder</Field.Label>
  <TimePicker clearable minuteStep={5} value={time} onValueChange={setTime}>
    <TimePicker.Trigger>
      <TimePicker.Value placeholder="Choose a reminder time" />
      <TimePicker.Icon />
    </TimePicker.Trigger>
    <TimePicker.Content title="Reminder time">
      <TimePicker.Wheel />
    </TimePicker.Content>
  </TimePicker>
  <Field.Error>{time === null ? "Pick a time for the reminder." : undefined}</Field.Error>
</Field>

Custom format

format replaces the locale's own reading of the time on the trigger.

<TimePicker
  format={(value) => `${value.hour}h${String(value.minute).padStart(2, "0")}`}
  value={time}
  onValueChange={setTime}
/>

Accessibility

The trigger is a button whose value is the formatted time, or "not set". Each wheel column is one adjustable element — "Hour", "Minute", "Second", "AM/PM" — so a screen reader swipes up or down to step a row instead of flicking a wheel. The rows themselves are hidden.

Time helpers

The subpath also exports the pure helpers the picker is built on, for a form that has to store, validate or show a time.

import { formatTime, parseTime, serialiseTime } from "@/components/ui/time-picker";

parseTime("09:30"); // { hour: 9, minute: 30 }
serialiseTime({ hour: 9, minute: 30 }); // "09:30"
formatTime({ hour: 15, minute: 5 }, { locale: "en-US", hourCycle: 12 }); // "3:05 PM"

API Reference

Anatomy

PartWhat it is
TimePickerOwns the value, the open state, the clock and the sheet
TimePicker.TriggerThe field-shaped control that opens the sheet
TimePicker.ValueThe formatted time, or the placeholder
TimePicker.IconThe trigger's glyph — a clock unless you pass another
TimePicker.ContentThe sheet: scrim, panel, title, body and footer
TimePicker.WheelThe wheel, wired to the picker
TimePicker.DoneCloses the sheet, committing an untouched wheel's time
TimePicker.ClearSets the value to null and closes

TimePicker

Prop

Type

TimePicker.Content

Prop

Type

TimePickerWheel

Prop

Type

useTimePicker

Reads the enclosing picker — value, setValue, isOpen, open, close, hourCycle, locale, display, isDisabled, isInvalid and the rest — for a custom trigger or footer. Throws outside a TimePicker; useTimePickerContext() returns null instead.

On this page