How to Add a Time Picker to a React App
Step-by-step guide to add a time picker to any React app with the Drivn CLI — a scrolling column dropdown with 12/24-hour clocks, seconds, and a native input.
Adding a time picker to a React app is one of those tasks that looks like a single <input> and turns into a project: the native <input type="time"> renders differently in every browser, and building your own dropdown means writing scroll logic, twelve-versus-twenty-four-hour math, AM/PM handling, and seconds — all by hand. Drivn skips that by handing you the finished control to copy into your project. The Time Picker is a scrolling column dropdown built on Drivn's own Popover and Input — no Radix, no cva, and no date library, just React, the local cn() helper, and a Clock icon from lucide-react.
Because it opens a popover and scrolls the selected value into view with a React.useEffect, its source begins with a 'use client' directive, so it renders inside a client component that owns the value. It is fully controlled: you hold the selected time as a plain Date in React.useState<Date | undefined>() and pass selected and onSelect, and every selection hands you back a real Date. This guide adds the Time Picker to any React app: install the CLI, render a default 24-hour picker, switch to a 12-hour clock with seconds, and use the native input variant in a form. For the App Router specifics see the Next.js Time Picker guide.
Prerequisites
Before installing the Time Picker, confirm your React project has what Drivn assumes: Tailwind CSS v4 processing your styles and a @/ path alias pointing at your source directory — the component ships as a .tsx file, so TypeScript is part of the stack. If you scaffolded with create-next-app, npm create vite, or npx drivn@latest create, both are already wired; the installation page lists the minimal config for a custom setup. Unlike a plain Input, the Time Picker composes two other Drivn components — the Popover for its dropdown surface and the Input for the native fallback — so the CLI resolves and writes those alongside it if they are not already present. And because it opens a popover and runs a scroll effect, its source carries a 'use client' directive: render it inside a client component, not directly in a Server Component.
Step 1 — Install Drivn via the CLI
Run the CLI from your project root to add the Time Picker source. Because the picker builds on the Drivn Popover and Input, the command resolves those dependencies and writes time-picker.tsx next to popover.tsx and input.tsx if they are not already there — one command, all three files, no runtime package to version-lock. The prompt defaults the install directory to src/components/ui/; the CLI docs cover the flags for targeting a custom path or adding several components at once. After install the files are yours: a future Drivn release will not overwrite your edits, and the whole picker — from the styles object to the Column sub-component and its scroll effect — is one short file you can read top to bottom. Commit it for a clean baseline before rendering it.
1 # add the Time Picker and its Popover + Input deps 2 npx drivn add time-picker
Step 2 — Render your first time picker
The simplest use is a controlled TimePicker in its default 24-hour mode. Mark your wrapper 'use client', hold the selected time in React.useState<Date | undefined>(), and pass selected and onSelect. The trigger shows a Clock icon with the formatted time, or the Pick a time placeholder when nothing is chosen. Opening it reveals two scrolling columns — hours 00–23 and minutes 00–59 — and a React.useEffect auto-centres the current value in each column so the selected number sits mid-list rather than at the top. Tapping a value calls onSelect with a fresh Date, so the time flows back to your component as a real Date you can store, format, or submit. This is the right default for most scheduling fields; the full prop list is on the Time Picker reference.
1 'use client' 2 3 import * as React from 'react' 4 import { TimePicker } from '@/components/ui/time-picker' 5 6 export function MeetingTime() { 7 const [time, setTime] = React.useState<Date | undefined>() 8 9 return <TimePicker selected={time} onSelect={setTime} /> 10 }
Step 3 — Switch to a 12-hour clock with seconds
To render a twelve-hour clock, pass format="12h". The hours column then runs 1–12 and an AM/PM switch appears beside the columns; internally the component derives the period from whether the hour is twelve or more and converts your AM/PM choice back to a 24-hour Date with its to24 helper, so onSelect always hands you a standard Date regardless of display. Add showSeconds for a third Sec column running 00–59, built from the same Column sub-component as the others, so it scrolls and auto-centres identically. When the built-in toLocaleTimeString label is not what you want — a specific locale, a shorter string — pass a formatTime function that receives the Date and returns the trigger text, leaving the columns untouched. Each of these is a plain prop on the same component, so moving from a bare 24-hour picker to a localized 12-hour clock with seconds is three attributes, not a rewrite. See the Time Picker examples for more layouts.
1 <TimePicker 2 selected={time} 3 onSelect={setTime} 4 format="12h" 5 showSeconds 6 />
Step 4 — Use the native time input in a form
When you would rather submit a value than manage a popover, the TimePicker.Input sub-component renders the Drivn Input as a native <input type="time">. It takes the same selected and onSelect pair, deriving its HH:MM value from the Date and parsing the browser's time control back into a Date on change. Give it a name and its value posts with the surrounding <form>, so a server handler reads it straight off the submitted data — and it accepts native min, max, and step attributes, for example step={900} to snap to fifteen-minute increments. The wrapper still owns the Date in state, so mark it 'use client', but the field itself submits like any other form control. This is the lighter choice for a settings row or a filter, where the native mobile time UI is worth more than the scrolling dropdown.
1 'use client' 2 3 import * as React from 'react' 4 import { TimePicker } from '@/components/ui/time-picker' 5 6 export function StartTime() { 7 const [time, setTime] = React.useState<Date | undefined>() 8 9 return ( 10 <TimePicker.Input 11 name="start" 12 selected={time} 13 onSelect={setTime} 14 /> 15 ) 16 }
Install Drivn in one command
Copy the source into your project and own every line. Zero runtime dependencies, pure React + Tailwind.
Frequently asked questions
Install the Drivn CLI and run npx drivn add time-picker, which writes time-picker.tsx along with its Popover and Input dependencies into your components directory. Import { TimePicker } from "@/components/ui/time-picker", hold the value in React.useState<Date | undefined>(), and pass selected and onSelect. The wrapper needs a 'use client' directive because the picker opens a popover and runs a scroll effect.
No. It works entirely with the native JavaScript Date object — it reads getHours, getMinutes, and getSeconds and builds a new Date on each selection. There is no date-fns, dayjs, or Luxon dependency, and no Radix. Past React its only imports are the Drivn Popover, the Drivn Input, the cn helper, and a Clock icon from lucide-react.
Pass format="12h" on the TimePicker. The hour column then renders 1 through 12, an AM/PM switch appears beside the columns, and the component converts your choice back to a 24-hour Date with its to24 helper. The default is format="24h", which shows hours 00 through 23 with no period switch. Add showSeconds for a third seconds column.
Yes. The TimePicker.Input sub-component renders a native <input type="time"> inside Drivn's Input. It derives an HH:MM value from the selected Date and parses changes back into a Date on onSelect. Give it a name so it posts with the surrounding form, and use native min, max, and step attributes to constrain the range — for example step={900} for fifteen-minute steps.
Because it holds interactive state: it opens a Popover and uses a React.useEffect to scroll the selected value into the centre of each column when the dropdown opens. Any component that renders hooks or effects like these must run on the client. You render the picker inside a client wrapper that owns the selected Date, then import that wrapper anywhere, including a Server Component page.

