Skip to content

Poursha98/react-ios-time-picker

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

30 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

react-ios-time-picker

A beautiful, accessible iOS-style time picker for React with Tailwind CSS and shadcn-style compound components. Styled with Tailwind out of the boxβ€”no CSS import needed. Features smooth scroll-snap physics, 12-hour AM/PM support, touch/mouse drag, RTL support, and full TypeScript types.

npm bundle size license

✨ Features

  • 🎑 iOS-style scroll physics - Native scroll-snap behavior
  • ♾️ Infinite Scrolling - Seamless looping of wheels (optional)
  • 🧩 Compound components - Mix and match parts like shadcn/ui
  • πŸ• 12-hour AM/PM support - Built-in period wheel
  • πŸ–±οΈ Multi-input support - Touch, mouse drag, and keyboard
  • β™Ώ Accessible - ARIA listbox pattern with full keyboard support
  • 🌐 RTL & Persian numerals - Built-in support for Persian/Arabic
  • 🎨 Tailwind-first - Styled with Tailwind CSS, no CSS import needed
  • 🎨 Fully customizable - Override styles easily via className prop
  • πŸ“¦ Lightweight - ~5KB gzipped
  • πŸ”§ TypeScript - Complete type definitions included

Note: Requires Tailwind CSS v3.0+ in your project

πŸ“¦ Installation

npm install @poursha98/react-ios-time-picker

Requirements: Tailwind CSS v3.0+ must be installed.

πŸš€ Quick Start

Simple Usage (All-in-One)

Simple Usage

import { useState } from "react";
import { TimePicker } from "@poursha98/react-ios-time-picker";
// No CSS import needed! ✨

function App() {
  const [time, setTime] = useState("09:30");

  return (
    <TimePicker
      value={time}
      onChange={setTime}
      onConfirm={() => console.log("Selected:", time)}
    />
  );
}

12-Hour Format with AM/PM

12-Hour Usage

import { useState } from "react";
import { TimePicker } from "@poursha98/react-ios-time-picker";

function App() {
  const [time, setTime] = useState("02:30 PM");

  return <TimePicker value={time} onChange={setTime} is12Hour />;
}

Infinite Scrolling (Loop)

Enable infinite looping for wheels (except AM/PM):

<TimePicker value={time} onChange={setTime} loop />

Customizing Styles

Easily override default styles with your own Tailwind classes:

<TimePicker
  value={time}
  onChange={setTime}
  className="bg-slate-900 p-8 rounded-3xl shadow-2xl" // Override container
/>

Or use compound components for granular control:

<TimePickerRoot
  value={time}
  onChange={setTime}
  className="bg-linear-to-br from-purple-500 to-pink-500 p-8"
>
  <TimePickerTitle className="text-white text-2xl">Pick a Time</TimePickerTitle>
  <TimePickerWheels>
    <TimePickerWheel type="hour" className="bg-white/20 backdrop-blur" />
    <TimePickerSeparator className="text-white">:</TimePickerSeparator>
    <TimePickerWheel type="minute" className="bg-white/20 backdrop-blur" />
  </TimePickerWheels>
  <TimePickerButton className="bg-white text-purple-600 hover:bg-gray-100">
    Confirm
  </TimePickerButton>
</TimePickerRoot>

Customizing Wheel Item Colors

Customize the colors of wheel items using the classNames prop:

<TimePickerWheel
  type="hour"
  classNames={{
    item: "text-gray-400", // Unselected items
    selectedItem: "text-primary", // Selected item
  }}
/>

You can also use Tailwind's arbitrary variant syntax to target data attributes:

<TimePickerWheel
  type="minute"
  className="[&_[data-wheel-item]]:text-gray-400 [&_[data-wheel-item][data-selected]]:text-blue-500"
/>

Compound Components (Full Control)

Compound Components

For maximum flexibility, use individual compound components:

import { useState } from "react";
import {
  TimePickerRoot,
  TimePickerTitle,
  TimePickerWheels,
  TimePickerWheel,
  TimePickerSeparator,
  TimePickerButton,
} from "@poursha98/react-ios-time-picker";

function CustomTimePicker() {
  const [time, setTime] = useState("09:30");

  return (
    <TimePickerRoot
      value={time}
      onChange={setTime}
      className="bg-slate-900 rounded-2xl p-6"
    >
      <TimePickerTitle className="text-white text-xl font-bold mb-4">
        ⏰ Select Time
      </TimePickerTitle>

      <TimePickerWheels className="flex justify-center items-center gap-2">
        <TimePickerWheel type="hour" className="bg-slate-800 rounded-lg" />

        <TimePickerSeparator className="text-blue-400 text-2xl font-bold">
          :
        </TimePickerSeparator>

        <TimePickerWheel type="minute" className="bg-slate-800 rounded-lg" />
      </TimePickerWheels>

      <TimePickerButton className="mt-6 w-full bg-blue-500 text-white py-3 rounded-xl">
        Confirm Selection
      </TimePickerButton>
    </TimePickerRoot>
  );
}

12-Hour with Compound Components

<TimePickerRoot value={time} onChange={setTime} is12Hour>
  <TimePickerWheels>
    <TimePickerWheel type="hour" />
    <TimePickerSeparator>:</TimePickerSeparator>
    <TimePickerWheel type="minute" />
    <TimePickerWheel type="period" /> {/* AM/PM wheel */}
  </TimePickerWheels>
  <TimePickerButton />
</TimePickerRoot>

Low-Level Wheel (Custom Pickers)

Build completely custom pickers using the base Wheel component:

import { useState } from "react";
import { Wheel } from "@poursha98/react-ios-time-picker";

const fruits = [
  "🍎 Apple",
  "🍊 Orange",
  "πŸ‹ Lemon",
  "πŸ‡ Grape",
  "πŸ“ Strawberry",
];

function FruitPicker() {
  const [selected, setSelected] = useState(0);

  return (
    <Wheel
      items={fruits}
      value={selected}
      onChange={setSelected}
      itemHeight={48}
      visibleCount={5}
    />
  );
}

πŸ“š API Reference

TimePicker Props (All-in-One)

Prop Type Default Description
value string required Time value ("HH:MM" or "HH:MM AM/PM")
onChange (time: string) => void required Called when time changes
onConfirm () => void - Called when confirm button is clicked
is12Hour boolean false Enable 12-hour format with AM/PM
numerals "en" | "fa" | "auto" "auto" Number format and text language
hours number[] [0-23] or [1-12] Custom hours array
minutes number[] [0-59] Custom minutes array
minuteStep number - Minute interval (5, 15, 30)
showTitle boolean true Show title
showLabels boolean true Show hour/minute labels
showConfirmButton boolean true Show confirm button
itemHeight number 48 Height of each wheel item
visibleCount number 5 Number of visible items
loop boolean false Enable infinite looping
disabled boolean false Disable the picker
className string - Root element className
classNames TimePickerClassNames - CSS class names (legacy)
styles TimePickerStyles - Inline styles (legacy)

Compound Components

TimePickerRoot

The context provider that wraps all other components.

<TimePickerRoot
  value={time}
  onChange={setTime}
  is12Hour={false}
  numerals="auto"
  disabled={false}
  loop={false}
  onConfirm={() => {}}
  className="my-picker"
>
  {children}
</TimePickerRoot>

TimePickerTitle

Displays the picker title. Uses <h2> by default.

<TimePickerTitle className="text-xl font-bold">Select Time</TimePickerTitle>;

{
  /* Use asChild for custom elements */
}
<TimePickerTitle asChild>
  <h1>Choose Time</h1>
</TimePickerTitle>;

TimePickerWheels

Container for the wheel columns.

<TimePickerWheels className="flex gap-2">
  {/* wheels go here */}
</TimePickerWheels>

TimePickerWheel

Individual wheel for hour, minute, or period (AM/PM).

<TimePickerWheel type="hour" className="w-20" />
<TimePickerWheel type="minute" className="w-20" />
<TimePickerWheel type="period" className="w-16" /> {/* AM/PM */}

TimePickerSeparator

The colon between wheels.

<TimePickerSeparator className="text-blue-500">:</TimePickerSeparator>

TimePickerLabel

Labels for each wheel column.

<TimePickerLabel type="hour">Hour</TimePickerLabel>
<TimePickerLabel type="minute">Minute</TimePickerLabel>

TimePickerButton

Confirm button that triggers onConfirm.

<TimePickerButton className="bg-blue-500 text-white px-4 py-2 rounded">
  Confirm
</TimePickerButton>;

{
  /* Use asChild for custom elements */
}
<TimePickerButton asChild>
  <a href="/next">Continue</a>
</TimePickerButton>;

Wheel Props

Prop Type Default Description
items T[] required Array of items to display
value number required Currently selected index
onChange (index: number) => void required Called when selection changes
itemHeight number 40 Height of each item in pixels
visibleCount number 5 Number of visible items
width string | number "100%" Width of the wheel
loop boolean false Enable infinite looping
renderItem (item, index, isSelected) => ReactNode - Custom item renderer
disabled boolean false Disable the wheel
classNames WheelClassNames - CSS class names
styles WheelStyles - Inline styles
aria-label string - Accessible label
getItemLabel (item, index) => string - Accessible item labels

🎨 Styling

CSS Variables

Customize colors using CSS variables:

:root {
  --time-picker-bg: #ffffff;
  --time-picker-text: #1f2937;
  --time-picker-text-secondary: #9ca3af;
  --time-picker-primary: #3b82f6;
  --time-picker-primary-light: rgba(59, 130, 246, 0.1);
}

/* Dark mode */
.dark {
  --time-picker-bg: #1f2937;
  --time-picker-text: #f3f4f6;
  --time-picker-text-secondary: #9ca3af;
  --time-picker-primary: #60a5fa;
  --time-picker-primary-light: rgba(96, 165, 250, 0.1);
}

Data Attribute Selectors

Style using data attributes for more specificity:

/* Root */
[data-time-picker] {
  background: #1e293b;
}

/* Title */
[data-time-picker-title] {
  color: white;
}

/* Wheel items - no default colors, customize as needed */
[data-wheel-item] {
  color: #94a3b8; /* Unselected items */
}

[data-wheel-item][data-selected] {
  color: #3b82f6; /* Selected item */
  font-weight: bold;
}

/* Wheel indicator */
[data-wheel-indicator] {
  border-color: #3b82f6;
}

Note: As of version 2.0, wheel items no longer have hardcoded text colors. You must explicitly set colors using the classNames prop, data attribute selectors, or the className prop on TimePickerWheel.

Tailwind CSS Example

<TimePickerRoot
  value={time}
  onChange={setTime}
  className="bg-slate-900 rounded-2xl p-6 shadow-xl"
>
  <TimePickerTitle className="text-white font-bold text-xl mb-4">
    Choose Time
  </TimePickerTitle>

  <TimePickerWheels className="flex justify-center gap-2">
    <TimePickerWheel type="hour" className="bg-slate-800 rounded-lg" />
    <TimePickerSeparator className="text-blue-400 text-2xl">
      :
    </TimePickerSeparator>
    <TimePickerWheel type="minute" className="bg-slate-800 rounded-lg" />
  </TimePickerWheels>

  <TimePickerButton className="mt-4 w-full bg-linear-to-r from-blue-500 to-purple-500 text-white py-3 rounded-xl hover:opacity-90 transition">
    Confirm
  </TimePickerButton>
</TimePickerRoot>

πŸ“ Examples

Persian/RTL Support

<TimePicker
  value={time}
  onChange={setTime}
  numerals="fa" // Persian numerals + Persian text
/>

Custom Minute Steps

// 15-minute intervals
<TimePicker
  value={time}
  onChange={setTime}
  minuteStep={15}
/>

// Or custom array
<TimePicker
  value={time}
  onChange={setTime}
  minutes={[0, 15, 30, 45]}
/>

React Hook Form Integration

import { Controller, useForm } from "react-hook-form";
import { TimePicker } from "@poursha98/react-ios-time-picker";

function MyForm() {
  const { control, handleSubmit } = useForm({
    defaultValues: { appointmentTime: "09:00" },
  });

  return (
    <form onSubmit={handleSubmit((data) => console.log(data))}>
      <Controller
        name="appointmentTime"
        control={control}
        render={({ field }) => (
          <TimePicker
            value={field.value}
            onChange={field.onChange}
            showConfirmButton={false}
          />
        )}
      />
      <button type="submit">Book Appointment</button>
    </form>
  );
}

Without Default Styles

Import only the components without any CSS:

import {
  TimePickerRoot,
  TimePickerWheel,
  TimePickerButton,
} from "@poursha98/react-ios-time-picker";
// No CSS import!

function MinimalPicker() {
  const [time, setTime] = useState("09:30");

  return (
    <TimePickerRoot value={time} onChange={setTime}>
      <TimePickerWheel type="hour" />
      <span>:</span>
      <TimePickerWheel type="minute" />
      <TimePickerButton>OK</TimePickerButton>
    </TimePickerRoot>
  );
}

β™Ώ Accessibility

  • Full keyboard navigation: Arrow keys, Home, End, Page Up/Down
  • ARIA listbox pattern with option roles
  • Screen reader announcements via aria-label and getItemLabel
  • Focus management and visible focus indicators
  • Unique ARIA IDs for multiple instances

🌐 Browser Support

  • Chrome, Edge, Safari, Firefox (latest 2 versions)
  • iOS Safari 13+
  • Android Chrome 80+

πŸ“„ License

MIT Β© Poursha98


About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors