Modern time picker library built with TypeScript. Works with any framework or vanilla JavaScript.
Live Demo • Documentation • React Wrapper • Changelog
Upgrading from v3? Check the upgrade guide below. Upgrading from v2? Check the v2 → v3 upgrade guide.
This project is actively maintained. Some areas planned for improvement:
Contributions welcome! Feel free to open an issue or PR.
npm install timepicker-ui
Your app needs this global CSS rule for correct styling:
*,
*::before,
*::after {
box-sizing: border-box;
}
Most projects include this by default.
<input id="timepicker" type="text" />
import { TimepickerUI } from "timepicker-ui";
import "timepicker-ui/main.css";
const input = document.querySelector("#timepicker");
const picker = new TimepickerUI(input);
picker.create();
const picker = new TimepickerUI(input, {
ui: {
theme: "dark",
animation: true,
backdrop: true,
},
clock: {
type: "24h",
},
callbacks: {
onConfirm: (data) => {
console.log("Selected time:", data);
},
},
});
picker.create();
import { useEffect, useRef } from "react";
import { TimepickerUI } from "timepicker-ui";
import "timepicker-ui/main.css";
function TimePickerComponent() {
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
if (inputRef.current) {
const picker = new TimepickerUI(inputRef.current, {
callbacks: {
onConfirm: (data) => {
console.log("Time selected:", data);
},
});
picker.create();
return () => picker.destroy();
}
}, []);
return <input ref={inputRef} type="text" />;
}
Full documentation available at timepicker-ui.vercel.app/docs
Options are now organized into 5 logical groups:
const picker = new TimepickerUI(input, {
clock: ClockOptions, // Clock behavior (type, increments, disabled time)
ui: UIOptions, // Appearance (theme, animation, mobile, inline)
labels: LabelsOptions, // Text labels (AM/PM, buttons, headers)
behavior: BehaviorOptions, // Behavior (focus, delays, ID)
callbacks: CallbacksOptions, // Event handlers
});
| Property | Type | Default | Description |
|---|---|---|---|
type |
12h / 24h |
12h |
Clock format |
incrementHours |
number | 1 |
Hour increment step |
incrementMinutes |
number | 1 |
Minute increment step |
autoSwitchToMinutes |
boolean | false |
Auto-switch after hour selected |
disabledTime |
object | undefined |
Disable specific hours/minutes |
currentTime |
boolean/object | undefined |
Set current time to input |
| Property | Type | Default | Description |
|---|---|---|---|
theme |
string | basic |
Theme (11 themes available) |
animation |
boolean | true |
Enable animations |
backdrop |
boolean | true |
Show backdrop overlay |
mobile |
boolean | false |
Force mobile version |
enableSwitchIcon |
boolean | false |
Show desktop/mobile switch icon |
editable |
boolean | false |
Allow manual input editing |
enableScrollbar |
boolean | false |
Enable scroll when picker open |
cssClass |
string | undefined |
Additional CSS class |
appendModalSelector |
string | "" |
Custom container selector |
iconTemplate |
string | SVG | Desktop switch icon template |
iconTemplateMobile |
string | SVG | Mobile switch icon template |
inline |
object | undefined |
Inline mode configuration |
| Property | Type | Default | Description |
|---|---|---|---|
am |
string | AM |
AM label text |
pm |
string | PM |
PM label text |
ok |
string | OK |
OK button text |
cancel |
string | Cancel |
Cancel button text |
time |
string | Select time |
Desktop time label |
mobileTime |
string | Enter Time |
Mobile time label |
mobileHour |
string | Hour |
Mobile hour label |
mobileMinute |
string | Minute |
Mobile minute label |
| Property | Type | Default | Description |
|---|---|---|---|
focusInputAfterClose |
boolean | false |
Focus input after close |
focusTrap |
boolean | true |
Trap focus in modal |
delayHandler |
number | 300 |
Click delay (ms) |
id |
string | auto-generated | Custom instance ID |
| Property | Type | Description |
|---|---|---|
onConfirm |
function | Time confirmed |
onCancel |
function | Cancelled |
onOpen |
function | Picker opened |
onUpdate |
function | Time updated (real-time) |
onSelectHour |
function | Hour selected |
onSelectMinute |
function | Minute selected |
onSelectAM |
function | AM selected |
onSelectPM |
function | PM selected |
onError |
function | Error occurred |
All options must be moved into groups:
// v3.x (DEPRECATED)
-const picker = new TimepickerUI(input, {
- clockType: '24h',
- theme: 'dark',
- animation: true,
- incrementMinutes: 5,
- amLabel: 'AM',
- onConfirm: (data) => {}
-});
// v4.0.0 (NEW)
+const picker = new TimepickerUI(input, {
+ clock: {
+ type: '24h',
+ incrementMinutes: 5
+ },
+ ui: {
+ theme: 'dark',
+ animation: true
+ },
+ labels: {
+ am: 'AM'
+ },
+ callbacks: {
+ onConfirm: (data) => {}
+ }
+});
Full migration table:
| v3.x (flat) | v4.0.0 (grouped) |
|---|---|
clockType |
clock.type |
incrementHours |
clock.incrementHours |
incrementMinutes |
clock.incrementMinutes |
autoSwitchToMinutes |
clock.autoSwitchToMinutes |
disabledTime |
clock.disabledTime |
currentTime |
clock.currentTime |
theme |
ui.theme |
animation |
ui.animation |
backdrop |
ui.backdrop |
mobile |
ui.mobile |
enableSwitchIcon |
ui.enableSwitchIcon |
editable |
ui.editable |
enableScrollbar |
ui.enableScrollbar |
cssClass |
ui.cssClass |
appendModalSelector |
ui.appendModalSelector |
iconTemplate |
ui.iconTemplate |
iconTemplateMobile |
ui.iconTemplateMobile |
inline |
ui.inline |
amLabel |
labels.am |
pmLabel |
labels.pm |
okLabel |
labels.ok |
cancelLabel |
labels.cancel |
timeLabel |
labels.time |
mobileTimeLabel |
labels.mobileTime |
hourMobileLabel |
labels.mobileHour |
minuteMobileLabel |
labels.mobileMinute |
focusInputAfterCloseModal |
behavior.focusInputAfterClose |
focusTrap |
behavior.focusTrap |
delayHandler |
behavior.delayHandler |
id |
behavior.id |
onConfirm |
callbacks.onConfirm |
onCancel |
callbacks.onCancel |
onOpen |
callbacks.onOpen |
onUpdate |
callbacks.onUpdate |
onSelectHour |
callbacks.onSelectHour |
onSelectMinute |
callbacks.onSelectMinute |
onSelectAM |
callbacks.onSelectAM |
onSelectPM |
callbacks.onSelectPM |
onError |
callbacks.onError |
Available themes: basic, crane, crane-straight, m3-green, m2, dark, glassmorphic, pastel, ai, cyberpunk
import "timepicker-ui/main.css"; // Required base styles
import "timepicker-ui/theme-dark.css"; // Specific theme
const picker = new TimepickerUI(input, {
ui: {
theme: "dark",
},
});
const picker = new TimepickerUI(input, {
clock: {
disabledTime: {
hours: [1, 3, 5, 8],
minutes: [15, 30, 45],
interval: "10:00 AM - 2:00 PM",
},
},
});
const picker = new TimepickerUI(input, {
ui: {
inline: {
enabled: true,
containerId: "timepicker-container",
showButtons: false,
autoUpdate: true,
},
},
});
const picker = new TimepickerUI(input, options);
picker.create(); // Initialize
picker.open(); // Open programmatically
picker.close(); // Close
picker.destroy(); // Clean up
picker.getValue(); // Get current time
picker.setValue("14:30"); // Set time
picker.update({ options }); // Update configuration
TimepickerUI.getById("my-id"); // Get instance by ID
TimepickerUI.getAllInstances(); // Get all instances
TimepickerUI.destroyAll(); // Destroy all instances
Listen to timepicker events using the EventEmitter API (v4+) or callback options:
const picker = new TimepickerUI(input);
picker.create();
picker.on("confirm", (data) => {
console.log("Time confirmed:", data);
});
picker.on("cancel", (data) => {
console.log("Cancelled:", data);
});
picker.on("open", () => {
console.log("Picker opened");
});
picker.on("update", (data) => {
console.log("Time updated:", data);
});
picker.on("select:hour", (data) => {
console.log("Hour selected:", data.hour);
});
picker.on("select:minute", (data) => {
console.log("Minute selected:", data.minutes);
});
picker.on("select:am", (data) => {
console.log("AM selected");
});
picker.on("select:pm", (data) => {
console.log("PM selected");
});
picker.on("error", (data) => {
console.log("Error:", data.error);
});
picker.once("confirm", (data) => {
console.log("This runs only once");
});
picker.off("confirm", handler);
⚠️ Only Available in v3.x - Completely Removed in v4.0.0
DOM events (e.g., timepicker:confirm) were removed in v4.0.0. Use the EventEmitter API shown above or callback options instead.
v3.x code (no longer works in v4):
input.addEventListener("timepicker:confirm", (e) => {
console.log("Time:", e.detail);
});
v4.0.0 alternatives:
// Option 1: EventEmitter API
picker.on("confirm", (data) => {
console.log("Time:", data);
});
// Option 2: Callback options
const picker = new TimepickerUI(input, {
callbacks: {
onConfirm: (data) => {
console.log("Time:", data);
},
},
});
Removed events: timepicker:open, timepicker:cancel, timepicker:confirm, timepicker:update, timepicker:select-hour, timepicker:select-minute, timepicker:select-am, timepicker:select-pm, timepicker:error
1. CSS Class Names Renamed
All CSS classes have been shortened from timepicker-ui-* to tp-ui-*:
/* v3 */
.timepicker-ui-wrapper {
}
.timepicker-ui-modal {
}
.timepicker-ui-clock-face {
}
.timepicker-ui-hour {
}
.timepicker-ui-minutes {
}
.timepicker-ui-am {
}
.timepicker-ui-pm {
}
/* v4 */
.tp-ui-wrapper {
}
.tp-ui-modal {
}
.tp-ui-clock-face {
}
.tp-ui-hour {
}
.tp-ui-minutes {
}
.tp-ui-am {
}
.tp-ui-pm {
}
Impact: If you have custom CSS targeting timepicker elements, update all class selectors.
2. Grouped Options Structure
All options are now organized into logical groups for better maintainability:
// v3
const picker = new TimepickerUI(input, {
clockType: "12h",
theme: "dark",
enableSwitchIcon: true,
mobile: true,
animation: true,
backdrop: true,
focusTrap: true,
editable: true,
onConfirm: (data) => console.log(data),
});
// v4 - Grouped options
const picker = new TimepickerUI(input, {
clock: {
type: "12h",
incrementHours: 1,
incrementMinutes: 1,
currentTime: { time: new Date(), updateInput: true },
disabledTime: { hours: [1, 2, 3] },
autoSwitchToMinutes: false,
},
ui: {
theme: "dark",
enableSwitchIcon: true,
mobile: true,
animation: true,
backdrop: true,
editable: true,
cssClass: "my-custom-class",
inline: { enabled: false },
},
labels: {
time: "Select Time",
am: "AM",
pm: "PM",
ok: "OK",
cancel: "Cancel",
},
behavior: {
focusTrap: true,
focusInputAfterClose: false,
delayHandler: 300,
},
callbacks: {
onConfirm: (data) => console.log(data),
onCancel: (data) => console.log(data),
onOpen: (data) => console.log(data),
onUpdate: (data) => console.log(data),
onSelectHour: (data) => console.log(data),
onSelectMinute: (data) => console.log(data),
onSelectAM: (data) => console.log(data),
onSelectPM: (data) => console.log(data),
onError: (data) => console.log(data),
},
});
2. Legacy DOM Events Removed
DOM events have been completely removed. Use EventEmitter API or callback options:
// v3 - Deprecated (removed in v4)
input.addEventListener("timepicker:confirm", (e) => {
console.log(e.detail);
});
// v4 - EventEmitter API (recommended)
picker.on("confirm", (data) => {
console.log(data);
});
// v4 - Or use callback options
const picker = new TimepickerUI(input, {
callbacks: {
onConfirm: (data) => console.log(data),
},
});
3. setTheme() Method Removed
Programmatic theme setting via setTheme() has been removed. Use CSS classes instead:
// v3 - Removed in v4
picker.setTheme({
primaryColor: "#ff0000",
backgroundColor: "#000000",
});
// v4 - Use CSS classes with CSS variables
const picker = new TimepickerUI(input, {
ui: { cssClass: "my-custom-theme" },
});
/* Define custom theme in CSS */
.tp-ui-wrapper.my-custom-theme {
--tp-primary: #ff0000;
--tp-bg: #000000;
--tp-surface: #f0f0f0;
}
4. SSR-Safe Architecture
All modules are now SSR-safe and can be imported in Node.js environments without crashing.
clock, ui, labels, behavior, callbacks groupspicker.on() EventEmitter API or callback optionson(), off(), once()1. CSS must be imported manually
// v3 - Required
import "timepicker-ui/main.css";
2. Event names changed
// v2
input.addEventListener("show", ...);
input.addEventListener("accept", ...);
// v3
input.addEventListener("timepicker:open", ...);
input.addEventListener("timepicker:confirm", ...);
3. Use callbacks instead of events
// v3 - Recommended
const picker = new TimepickerUI(input, {
onConfirm: (data) => console.log(data),
onCancel: (data) => console.log("Cancelled"),
});
4. destroy() behavior
// v2 - Removed input from DOM
picker.destroy();
// v3 - Only destroys picker, keeps input
picker.destroy();
getById, destroyAll)getValue() and setValue() methodson, off, once)Starting in v3.x, we recommend using the new EventEmitter API instead of DOM events:
// Old way (deprecated, will be removed in v4)
input.addEventListener("timepicker:confirm", (e) => {
console.log(e.detail);
});
// New way (recommended)
picker.on("confirm", (data) => {
console.log(data);
});
Benefits:
once() for one-time listenersimport { useEffect, useRef } from "react";
import { TimepickerUI } from "timepicker-ui";
import "timepicker-ui/main.css";
function App() {
const inputRef = useRef(null);
useEffect(() => {
if (!inputRef.current) return;
const picker = new TimepickerUI(inputRef.current);
picker.create();
return () => picker.destroy();
}, []);
return <input ref={inputRef} placeholder="Select time" />;
}
<template>
<input ref="pickerInput" placeholder="Select time" />
</template>
<script setup>
import { onMounted, ref } from "vue";
import { TimepickerUI } from "timepicker-ui";
import "timepicker-ui/main.css";
const pickerInput = ref(null);
onMounted(() => {
if (!pickerInput.value) return;
const picker = new TimepickerUI(pickerInput.value);
picker.create();
});
</script>
import { Component, ElementRef, ViewChild, AfterViewInit } from "@angular/core";
import { TimepickerUI } from "timepicker-ui";
@Component({
selector: "app-root",
template: `<input #timepickerInput placeholder="Select time" />`,
})
export class App implements AfterViewInit {
@ViewChild("timepickerInput") inputRef!: ElementRef<HTMLInputElement>;
ngAfterViewInit() {
const picker = new TimepickerUI(this.inputRef.nativeElement);
picker.create();
}
}
Add to angular.json styles:
"styles": ["src/styles.css", "timepicker-ui/main.css"]
Development tooling is in the app/ directory. See app/README.md for details.
MIT © Piotr Glejzer
Contributions welcome! Check the issues page.
Chrome 60+, Firefox 55+, Safari 12+, Edge 79+, iOS Safari 12+, Chrome Android 60+