Highly customizable time picker UI library for JavaScript and modern frameworks (React, Vue, Angular), with clock and wheel modes, zero dependencies, and full SSR support.
Live Demo • Documentation • Changelog • React Wrapper
- Zero dependencies - no runtime deps, smaller bundle, no supply-chain risk
- Multiple UI modes - analog clock, scroll wheel, compact popover - not just one layout
- Plugin architecture - range, timezone, wheel - import only what you need
- SSR safe - works in Next.js, Nuxt, Remix, Astro out of the box
- Any framework - React, Vue, Angular, Svelte, or plain JS - same API everywhere
- Accessible - ARIA, keyboard nav, focus trap, screen reader support
- Booking forms, scheduling, reservations
- Admin panels and dashboards
- Mobile-first apps with touch/scroll UX
- Time range selection (via Range plugin)
- SSR applications (Next.js, Nuxt, Remix, Astro)
| timepicker-ui | Typical UI lib | |
|---|---|---|
| Dependencies | 0 | 1-10+ |
| UI modes | Clock + Wheel + Compact | Clock only |
| Plugin system | Yes (range, timezone, wheel) | No |
| Framework lock-in | None - works everywhere | Often React-only |
| SSR safe | Yes, out of the box | Often requires workarounds |
| Tree-shakeable | Yes - plugins excluded if unused | Varies |
- 10 built-in themes (Material, Crane, Dark, Glassmorphic, Cyberpunk, and more)
- Analog clock, wheel, and compact-wheel picker modes
- Inline mode, clear button, disabled time ranges
- Mobile-first with touch & keyboard support
- Framework agnostic - React, Vue, Angular, Svelte, vanilla JS
- Full TypeScript support, SSR compatible, true tree-shaking
Full guide: Installation docs
npm install timepicker-uiFull guide: Quick Start docs
<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, {
clock: { type: "24h" },
ui: { theme: "dark" },
callbacks: {
onConfirm: (data) => console.log("Selected:", data),
},
});
picker.create();Note - Requires global
box-sizing: border-box(included by default in most frameworks) .
Full reference: Methods · Events · TypeScript
picker.create(); // Initialize
picker.open(); // Open programmatically
picker.close(); // Close
picker.destroy(); // Clean up
picker.getValue(); // Get current time string
picker.setValue("14:30"); // Set time
picker.update({ ... }); // Update options at runtime
// Events
picker.on("confirm", (data) => {});
picker.on("cancel", (data) => {});
picker.on("open", () => {});
picker.on("update", (data) => {});
picker.on("clear", (data) => {});
picker.on("error", (data) => {});
picker.once("confirm", handler);
picker.off("confirm", handler);
// Static
TimepickerUI.getById("my-id");
TimepickerUI.getAllInstances();
TimepickerUI.destroyAll();Options are grouped into logical namespaces. Full reference: Options docs · Configuration guide
new TimepickerUI(input, {
clock: {
type: "12h" | "24h", // default: "12h"
incrementHours: 1,
incrementMinutes: 1,
disabledTime: { hours: [], minutes: [], interval: "" },
currentTime: boolean | object,
},
ui: {
theme: "basic" | "dark" | "m3-green" | "crane" | ..., // 10 themes
mode: "clock" | "wheel" | "compact-wheel", // default: "clock"
animation: true,
backdrop: true,
mobile: false,
editable: false,
inline: { enabled: false, containerId: "", showButtons: true, autoUpdate: false },
clearButton: false,
cssClass: "",
},
labels: { am, pm, ok, cancel, time, mobileTime, mobileHour, mobileMinute, clear },
behavior: { focusTrap: true, focusInputAfterClose: false, delayHandler: 300, id: "" },
callbacks: { onConfirm, onCancel, onOpen, onUpdate, onSelectHour, onSelectMinute, onSelectAM, onSelectPM, onError, onClear },
wheel: { placement: "auto" | "top" | "bottom", hideFooter: false, commitOnScroll: false, hideDisabled: false, ignoreOutsideClick: false },
});Browse all themes: Theme docs · Live examples · Custom styling
Available: basic, crane, crane-straight, m3-green, m2, dark, glassmorphic, pastel, ai, cyberpunk
import "timepicker-ui/main.css"; // always required
import "timepicker-ui/theme-dark.css"; // theme-specific stylesheet
new TimepickerUI(input, { ui: { theme: "dark" } });Docs: Plugins overview · Examples: Range · Timezone · Wheel
import { TimepickerUI, PluginRegistry } from "timepicker-ui";
import { RangePlugin } from "timepicker-ui/plugins/range";
import { TimezonePlugin } from "timepicker-ui/plugins/timezone";
import { WheelPlugin } from "timepicker-ui/plugins/wheel";
// Register once at app startup
PluginRegistry.register(RangePlugin);
PluginRegistry.register(TimezonePlugin);
PluginRegistry.register(WheelPlugin);
new TimepickerUI(input, { range: { enabled: true } });- v3 → v4 - grouped options, EventEmitter API, CSS classes renamed. See Migration Guide · Breaking Changes · CHANGELOG
- v2 → v3 - CSS must be imported manually, event names changed. See Legacy Migration · CHANGELOG
Full examples: React · Vue / Angular / Svelte
React (quick example)
import { useEffect, useRef } from "react";
import { TimepickerUI } from "timepicker-ui";
import "timepicker-ui/main.css";
function TimePicker() {
const ref = useRef<HTMLInputElement>(null);
useEffect(() => {
if (!ref.current) return;
const picker = new TimepickerUI(ref.current);
picker.create();
return () => picker.destroy();
}, []);
return <input ref={ref} />;
}There is also a dedicated React wrapper package.
- Tree-shakeable - unused plugins are fully excluded from the bundle
- Lightweight core - with tree-shaking support
- Plugins loaded on demand - range, timezone, wheel add size only when imported
- No runtime dependencies - nothing extra to download or audit
See app/README.md for local development setup.
Contributions welcome! Open an issue or PR.
MIT © Piotr Glejzer