react-ios-style-time-picker
v0.1.0
Published
A lightweight, customizable iOS-style rolling wheel time picker component for React.
Maintainers
Readme
React iOS Style Time Picker

A lightweight, customizable iOS-style rolling wheel time picker component for your next React app — supports 12/24-hour format, infinite scroll, and multiple locales.
Demo
Check out the live demo here: Live Demo
install
npm i react-ios-style-time-pickerUsage
12 hours format
import { useState } from 'react';
import { TimePicker } from 'react-ios-style-time-picker';
import 'react-ios-style-time-picker/style.css';
function App() {
const [time, setTime] = useState<{ hour: number; minute: number }>({
hour: new Date().getHours(),
minute: new Date().getMinutes(),
});
const handleTimeChange = (hour: number, minute: number) => {
setTime({ hour, minute });
};
return (
<div>
<TimePicker onChange={handleTimeChange} hourFormat='12' />
</div>
);
}24 hours format
import { useState } from 'react';
import { TimePicker } from 'react-ios-style-time-picker';
import 'react-ios-style-time-picker/style.css';
function App() {
const [time, setTime] = useState<{ hour: number; minute: number }>({
hour: new Date().getHours(),
minute: new Date().getMinutes(),
});
const handleTimeChange = (hour: number, minute: number) => {
setTime({ hour, minute });
};
return (
<div>
<TimePicker onChange={handleTimeChange} hourFormat='24' />
</div>
);
}Controlled
Pass value and update it from onChange, like a React <input>. Changing value
from outside (buttons, API responses, resets) moves the wheels without recreating
the picker and without calling onChange.
import { useState } from 'react';
import { TimePicker, type TimePickerValue } from 'react-ios-style-time-picker';
import 'react-ios-style-time-picker/style.css';
function App() {
const [time, setTime] = useState<TimePickerValue>({ hour: 9, minute: 0 });
return (
<div>
<TimePicker
value={time}
onChange={(hour, minute) => setTime({ hour, minute })}
/>
<button onClick={() => setTime({ hour: 18, minute: 30 })}>6:30 PM</button>
</div>
);
}Uncontrolled
<TimePicker
defaultValue={{ hour: 7, minute: 30 }}
onChange={(hour, minute) => console.log(hour, minute)}
/>Props
| Prop | Type | Required | Default | Description |
| :------------- | :--------------------------------------- | :------- | :----------- | :--------------------------------------------------------------------------------------------------- |
| onChange | (hour: number, minute: number) => void | ✅ | - | Called (debounced 100ms) when the user changes the time. hour is always 0-23 |
| value | { hour: number; minute: number } | ❌ | undefined | Controlled time (hour 0-23, minute 0-59). Changes move the wheels without firing onChange |
| defaultValue | { hour: number; minute: number } | ❌ | current time | Initial time for uncontrolled usage. Ignored when value is set |
| initTime | Date | ❌ | new Date() | Deprecated — alias of defaultValue, kept for backward compatibility |
| infinite | boolean | ❌ | false | Enables infinite scroll style |
| className | string | ❌ | undefined | Custom class name for styling |
| hourFormat | '12' | '24' | ❌ | '12' | Time format (12-hour/24-hour) |
| locale | 'en' | 'ko' | 'ja' | 'zh' | ❌ | 'en' | Language for AM/PM (English, Korean, Japanese, Chinese). Changes are applied in place |
Exported types: TimePickerProps, TimePickerValue, TimePickerLocale.
Behavior notes
- Initial value priority:
value→defaultValue→initTime→ current time.defaultValue/initTimeare read on mount only. onChangeon mount: in uncontrolled modeonChangeis called once after mount with the initial time (same as previous versions). In controlled mode it is not.- Controlled mode: user gestures move the wheels immediately and report through
onChange; updatevaluewith the reported time. Settingvalueto the time the picker already shows is a no-op, so echoingonChangeback never loops. If you do not updatevalue, the wheels keep the user's selection untilvaluechanges. Avaluechange during a drag is overridden when the drag ends. Invalid values are ignored with a console warning. - Rebuilds: changing
hourFormatorinfinitere-renders the wheels (their DOM structure differs) and keeps the currently selected time.localeonly swaps the AM/PM labels and does not rebuild.
Migrating from initTime
initTime still works but is deprecated and may be removed in a future major version.
- <TimePicker initTime={new Date(2025, 0, 1, 7, 30)} onChange={handleChange} />
+ <TimePicker defaultValue={{ hour: 7, minute: 30 }} onChange={handleChange} />Performance
While a wheel moves, only items that enter or leave the visible range have their
visibility updated, instead of every item on every animation frame. For a 60-item
minute wheel, a 60-frame flick went from 3,600 to 60 style.visibility writes.
See BENCHMARK.md for the method, all scenarios and how to reproduce.
Time Format (hourFormat)
12: Displays AM/PM notation24: Displays 0-23 hour format
Get involved!
We appreciate your feedback and contributions. If you have feature requests, questions, or want to contribute code or config files, please don't hesitate to use the GitHub Issue tracker.
We welcome all individual contributors, regardless of their level of experience or skill set. Your contributions are valuable, and we are excited to see what you can accomplish in this collaborative and supportive environment.
Reference
Inspired by ios-style-picker
It's forked from this gist
License
The MIT License.
