β οΈ Upgrading from v2 to v3?
Major changes were introduced in version 3.0.
π Click here to view the Upgrade Guide
A modern, lightweight, and fully customizable time picker library built with TypeScript. Features Googleβs Material Design principles with extensive theming support and framework-agnostic architecture.
Curious how it works in practice?
π Click here to see live examples
This project is actively maintained and evolving. Some areas are planned for future improvements:
any
types in the codebase β will be replaced with strict typingsIf youβre interested in contributing to any of these areas, feel free to open an issue or pull request!
npm install timepicker-ui
# or
yarn add timepicker-ui
For correct styling, make sure your app includes this global CSS rule:
*,
*::before,
*::after {
box-sizing: border-box;
}
This is a common default in most projects and is required by timepicker-ui
to avoid layout issues.
<input id="timepicker" type="text" />
import { TimepickerUI } from "timepicker-ui";
const input = document.querySelector("#timepicker");
const picker = new TimepickerUI(input);
picker.create();
const picker = new TimepickerUI(input, {
theme: "dark",
clockType: "24h",
animation: true,
backdrop: true,
});
picker.create();
import { useEffect, useRef } from "react";
import { TimepickerUI } from "timepicker-ui";
function TimePickerComponent() {
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
if (inputRef.current) {
const picker = new TimepickerUI(inputRef.current, {
onConfirm: (data) => {
console.log("Time selected:", data);
},
});
picker.create();
return () => picker.destroy();
}
}, []);
return <input ref={inputRef} type="text" />;
}
Option | Type | Default | Description |
---|---|---|---|
amLabel |
string |
"AM" |
Custom text for AM label |
animation |
boolean |
true |
Enable/disable open/close animations |
appendModalSelector |
string |
"" |
DOM selector to append timepicker (defaults to body) |
backdrop |
boolean |
true |
Show/hide backdrop overlay |
cancelLabel |
string |
"CANCEL" |
Text for cancel button |
clockType |
"12h" \| "24h" |
"12h" |
Clock format type |
cssClass |
string |
undefined |
Additional CSS class for timepicker wrapper |
currentTime |
boolean \| object |
undefined |
Set current time to input and picker |
delayHandler |
number |
300 |
Debounce delay for buttons (ms) |
disabledTime |
object |
undefined |
Disable specific hours, minutes, or intervals |
editable |
boolean |
false |
Allow manual input editing |
enableScrollbar |
boolean |
false |
Keep page scroll when picker is open |
enableSwitchIcon |
boolean |
false |
Show desktop/mobile switch icon |
focusInputAfterCloseModal |
boolean |
false |
Focus input after closing modal |
focusTrap |
boolean |
true |
Trap focus within modal |
hourMobileLabel |
string |
"Hour" |
Hour label for mobile version |
iconTemplate |
string |
Material Icons | HTML template for desktop switch icon |
iconTemplateMobile |
string |
Material Icons | HTML template for mobile switch icon |
id |
string |
undefined |
Custom ID for timepicker instance |
incrementHours |
number |
1 |
Hour increment step (1, 2, 3) |
incrementMinutes |
number |
1 |
Minute increment step (1, 5, 10, 15) |
inline |
object |
undefined |
Inline mode configuration |
minuteMobileLabel |
string |
"Minute" |
Minute label for mobile version |
mobile |
boolean |
false |
Force mobile version |
mobileTimeLabel |
string |
"Enter Time" |
Time label for mobile version |
okLabel |
string |
"OK" |
Text for OK button |
pmLabel |
string |
"PM" |
Custom text for PM label |
switchToMinutesAfterSelectHour |
boolean |
true |
Auto-switch to minutes after hour selection |
theme |
Theme | "basic" |
UI theme (see themes section) |
timeLabel |
string |
"Select Time" |
Time label for desktop version |
const picker = new TimepickerUI(input, {
inline: {
enabled: true,
containerId: "timepicker-container",
showButtons: false, // Hide OK/Cancel buttons
autoUpdate: true, // Auto-update input on change
},
});
const picker = new TimepickerUI(input, {
disabledTime: {
hours: [1, 3, 5, 8], // Disable specific hours
minutes: [15, 30, 45], // Disable specific minutes
interval: "10:00 AM - 2:00 PM", // Disable time range
},
});
Note: As of v3.0, you must import CSS styles manually. See Upgrade Guide for details.
Choose from 9 built-in themes:
Theme | Description |
---|---|
basic |
Default Material Design theme |
crane-straight |
Google Crane theme with straight edges |
crane-radius |
Google Crane theme with rounded edges |
m3 |
Material Design 3 (Material You) |
dark |
Dark mode theme |
glassmorphic |
Modern glass effect |
pastel |
Soft pastel colors |
ai |
Futuristic AI-inspired theme |
cyberpunk |
Neon cyberpunk aesthetic |
const picker = new TimepickerUI(input, {
theme: "cyberpunk",
});
Configure callback functions to handle timepicker events:
Callback | Type | Description |
---|---|---|
onOpen |
(data) => void |
Triggered when timepicker opens |
onCancel |
(data) => void |
Triggered when picker is cancelled |
onConfirm |
(data) => void |
Triggered when time is confirmed (OK clicked) |
onUpdate |
(data) => void |
Triggered during clock interaction (real-time) |
onSelectHour |
(data) => void |
Triggered when hour mode is activated |
onSelectMinute |
(data) => void |
Triggered when minute mode is activated |
onSelectAM |
(data) => void |
Triggered when AM is selected |
onSelectPM |
(data) => void |
Triggered when PM is selected |
onError |
(data) => void |
Triggered when invalid time format is detected |
interface CallbackData {
hour?: string;
minutes?: string;
type?: string; // 'AM' or 'PM'
degreesHours?: number;
degreesMinutes?: number;
error?: string; // Only for onError
// ... additional context data
}
const picker = new TimepickerUI(input, {
onConfirm: (data) => {
console.log(`Time selected: ${data.hour}:${data.minutes} ${data.type}`);
},
onCancel: (data) => {
console.log("User cancelled time selection");
},
onError: (data) => {
alert(`Invalid time format: ${data.error}`);
},
});
Listen to DOM events dispatched on the input element:
Event | Description |
---|---|
timepicker:open |
Fired when timepicker opens |
timepicker:cancel |
Fired when user cancels |
timepicker:confirm |
Fired when time is confirmed |
timepicker:update |
Fired during clock interaction |
timepicker:select-hour |
Fired when hour mode is selected |
timepicker:select-minute |
Fired when minute mode is selected |
timepicker:select-am |
Fired when AM is selected |
timepicker:select-pm |
Fired when PM is selected |
timepicker:error |
Fired when input validation fails |
const input = document.querySelector("#timepicker");
const picker = new TimepickerUI(input);
picker.create();
// Listen to events
input.addEventListener("timepicker:confirm", (e) => {
console.log("Time confirmed:", e.detail);
// e.detail contains: { hour, minutes, type, degreesHours, degreesMinutes }
});
input.addEventListener("timepicker:cancel", (e) => {
console.log("Cancelled:", e.detail);
});
input.addEventListener("timepicker:error", (e) => {
console.error("Error:", e.detail.error);
});
const picker = new TimepickerUI(input, options);
// Core methods
picker.create(); // Initialize the timepicker
picker.open(); // Open the timepicker programmatically
picker.close(); // Close the timepicker
picker.destroy(); // Destroy instance and clean up
// Value methods
picker.getValue(); // Get current time value
picker.setValue("14:30"); // Set time programmatically
// Configuration methods
picker.update({ options: newOptions }); // Update configuration
picker.getElement(); // Get the DOM element
// Instance management
TimepickerUI.getById("my-id"); // Get instance by ID
TimepickerUI.getAllInstances(); // Get all active instances
TimepickerUI.destroyAll(); // Destroy all instances
TimepickerUI.isAvailable(element); // Check if element exists
// Get current value
const currentTime = picker.getValue();
console.log(currentTime);
// Output: { hour: '14', minutes: '30', type: '', time: '14:30', degreesHours: 30, degreesMinutes: 180 }
// Set new time
picker.setValue("09:15 AM");
// Update configuration
picker.update({
options: { theme: "dark", clockType: "24h" },
create: true, // Reinitialize after update
});
// Instance management
const picker1 = new TimepickerUI("#picker1", { id: "picker-1" });
const picker2 = new TimepickerUI("#picker2", { id: "picker-2" });
// Later...
const foundPicker = TimepickerUI.getById("picker-1");
getById()
, destroyAll()
, and custom instance IDsdark
, glassmorphic
, pastel
, ai
, cyberpunk
getValue()
, setValue()
, improved destroy()
timepicker:
prefix.destroy()
no longer removes input from DOMStyles are no longer auto-loaded You must now explicitly import CSS files. Use:
main.css
β core styles with basic
theme onlyindex.css
β all styles including all themesthemes/
v2 (Old):
input.addEventListener('show', (e) => { ... });
input.addEventListener('cancel', (e) => { ... });
input.addEventListener('accept', (e) => { ... });
v3 (New):
input.addEventListener('timepicker:open', (e) => { ... });
input.addEventListener('timepicker:cancel', (e) => { ... });
input.addEventListener('timepicker:confirm', (e) => { ... });
v2 (Old):
const picker = new TimepickerUI(input);
input.addEventListener("accept", (e) => {
console.log("Time selected:", e.detail);
});
v3 (New):
const picker = new TimepickerUI(input, {
onConfirm: (data) => {
console.log("Time selected:", data);
},
});
v2 (Old):
picker.destroy(); // This removed the input from DOM
v3 (New):
picker.destroy(); // Only destroys timepicker, keeps input intact
// If you need to remove input, do it manually:
// input.remove();
v2 (Old):
// Limited theme options
theme: "basic" | "crane-straight" | "crane-radius" | "m3";
v3 (New):
// Extended theme options
theme: "basic" |
"crane-straight" |
"crane-radius" |
"m3" |
"dark" |
"glassmorphic" |
"pastel" |
"ai" |
"cyberpunk";
v3 New Feature:
const picker = new TimepickerUI(input, {
inline: {
enabled: true,
containerId: "timepicker-container",
showButtons: false,
autoUpdate: true,
},
});
v3 New Feature:
// Create with custom ID
const picker = new TimepickerUI(input, { id: "my-timepicker" });
// Later, get by ID
const foundPicker = TimepickerUI.getById("my-timepicker");
// Destroy all instances
TimepickerUI.destroyAll();
v2 (Old): Styles were bundled and automatically injected.
v3 (New): You must now import the styles yourself:
import "timepicker-ui/index.css";
import "timepicker-ui/main.css";
import "timepicker-ui/main.css"; // Required base
import "timepicker-ui/theme-dark.css"; // Or any other theme
import { useEffect, useRef } from "react";
import { TimepickerUI } from "timepicker-ui";
import "timepicker-ui/main.css";
import "timepicker-ui/theme-cyberpunk.css";
function App() {
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
if (!inputRef.current) return;
const picker = new TimepickerUI(inputRef.current, {
theme: "cyberpunk",
});
picker.create();
return () => {
picker.destroy();
};
}, []);
return (
<div style=>
<h1>Timepicker UI React Demo</h1>
<input ref={inputRef} placeholder="Click me..." />
</div>
);
}
export default App;
βΉοΈ Note: Donβt forget to include global
box-sizing
rule in yourstyles.css
(see Global CSS Required).
<template>
<div style="padding: 2rem">
<h1>Timepicker UI β Vue Demo</h1>
<input ref="pickerInput" placeholder="Pick a time..." />
</div>
</template>
<script setup>
import { onMounted, ref } from "vue";
import { TimepickerUI } from "timepicker-ui";
import "timepicker-ui/main.css";
import "timepicker-ui/theme-glassmorphic.css";
const pickerInput = ref(null);
onMounted(() => {
if (!pickerInput.value) return;
const picker = new TimepickerUI(pickerInput.value, {
theme: "glassmorphic",
});
picker.create();
});
</script>
βΉοΈ Note: Donβt forget to include global
box-sizing
rule in yourstyles.css
(see Global CSS Required).
app.ts
import {
Component,
ElementRef,
AfterViewInit,
ViewChild,
signal,
} from "@angular/core";
import { TimepickerUI } from "timepicker-ui";
@Component({
selector: "app-root",
standalone: true,
templateUrl: "./app.html",
styleUrl: "./app.css",
})
export class App implements AfterViewInit {
protected readonly title = signal("timepicker-ui-demo");
@ViewChild("timepickerInput", { static: true })
inputRef!: ElementRef<HTMLInputElement>;
ngAfterViewInit(): void {
const picker = new TimepickerUI(this.inputRef.nativeElement, {
theme: "glassmorphic",
});
picker.create();
}
}
app.html
<input #timepickerInput placeholder="Select time..." />
angular.json
β styles section"styles": [
"src/styles.css",
"timepicker-ui/main.css"
]
βΉοΈ Note: Donβt forget to include global
box-sizing
rule in yourstyles.css
(see Global CSS Required).
All development and build tooling is located in the app/
directory.
Please refer to app/README.md
for instructions on running the development server, building the library, running tests, and using the full toolchain.
MIT Β© Piotr Glejzer
Contributions, issues, and feature requests are welcome! Feel free to check the issues page.