timepicker-ui
| Install | |
|---|---|
npm install timepicker-ui |
|
| Latest Version: | 4.6.0 |
| License: | MIT |
| Last Updated: | Oct 19, 2026 |
| Links: | GitHub · npm |
timepicker-ui
A time picker for web forms. Attach it to an <input> and users pick a time on a clock face or a scroll wheel.
Live demo · Documentation · Changelog · React wrapper
Why timepicker-ui
- Zero dependencies.
- ~27 KB gzipped core. The range plugin is about 5 KB, the timezone plugin about 3 KB, the wheel plugin about 10 KB, and a plugin only gets bundled if you import it (bundle stats).
- Clock, wheel or compact wheel (a small dropdown under the input).
- Plain JS API, so it works the same in React, Vue, Angular, Svelte or without a framework.
- Doesn't touch
windowordocumenton import, so it's safe in Next.js, Nuxt and other SSR setups. - Keyboard and screen reader support. All texts, the screen-reader ones included, can be translated via
labels.
Features
- 12h / 24h, hour and minute steps, disabled times
- Optional seconds (
HH:MM:SS) in the clock, wheel and compact wheel - 12 themes (Material 2/3, Crane, Dark, Cyberpunk, Blueprint, ...)
- Inline mode (with an embedded variant for your own dialog), clear button, mobile layout
- Time ranges (linked or independent From/To) and timezones via plugins
- Disable times with a function (
clock.isTimeDisabled) - TypeScript types included
Installation
npm install timepicker-ui
Quick start
<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();
No global CSS reset is needed: the picker sets box-sizing: border-box on its own elements (details).
API
picker.create(); // render the picker and attach it to the input
picker.open();
picker.close();
picker.destroy();
picker.getValue(); // { hour, minutes, seconds, type, time }, seconds only with clock.showSeconds
picker.setValue("14:30"); // "2:30 PM" works too
picker.setValue("14:30:15"); // with clock.showSeconds
picker.setOptions({ ui: { theme: "crane" } });
picker.setRange({ from: "09:00", to: "17:00" }); // needs RangePlugin
picker.getRange();
picker.setActivePart("to");
picker.refreshDisabledTime(); // after the data behind clock.isTimeDisabled changes
const off = picker.on("confirm", (data) => {}); // on() and once() return a function that removes the handler
off();
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);
TimepickerUI.getById("my-id");
TimepickerUI.getAllInstances();
TimepickerUI.isAvailable("#timepicker");
TimepickerUI.destroyAll();
Options
Grouped by area. Pass only what you need, the rest has defaults (full list).
new TimepickerUI(input, {
clock: {
type: "12h" | "24h" | "auto", // default "12h"; "auto" follows the browser locale
incrementHours: 1,
incrementMinutes: 1,
showSeconds: false, // true adds seconds (HH:MM:SS)
incrementSeconds: 1,
disabledTime: { hours: [], minutes: [], seconds: [], ranges: "" },
isTimeDisabled: ({ hour, minutes, seconds }) => false, // hour is 0-23
defaultTime: "now" | { time, updateInput },
},
ui: {
theme: "basic" | "dark" | "m3-green" | "crane" | "blueprint" | ...,
mode: "clock" | "wheel" | "compact-wheel", // default "clock"
animation: true,
backdrop: true,
initialView: "clock", // or "keyboard"
editable: false,
inline: { enabled: false, container: "#picker" | element, showButtons: false, autoUpdate: false, embedded: false },
clearButton: false, // or { clearInput: false } to keep the input value on Clear
cssClass: "",
},
labels: { am, pm, ok, cancel, clear, clockTitle, hour, minute, second, rangeFrom, rangeTo, timezone, ... },
behavior: { focusTrap: true, returnFocus: false, delayHandler: 0, id: "" },
callbacks: { onConfirm, onCancel, onOpen, onUpdate, onSelectHour, onSelectMinute, onSelectSecond, onSelectPeriod, onClear, onError, ... },
wheel: { placement: "auto" | "top" | "bottom", hideFooter: false, commitOnScroll: false, hideDisabled: false, ignoreOutsideClick: false },
range: { enabled: false, linked: true, minDuration, maxDuration },
timezone: { enabled: false, default, list },
});
Themes
Preview all of them: basic, crane, crane-straight, m3-green, m2, dark, glassmorphic, pastel, ai, cyberpunk, blueprint, blueprint-dark.
main.css is required and already includes basic. The rest are separate files:
import "timepicker-ui/main.css";
import "timepicker-ui/theme-dark.css";
new TimepickerUI(input, { ui: { theme: "dark" } });
Or import timepicker-ui/index.css to get all of them. Writing your own theme: styling guide.
Plugins
Range, timezone and wheel ship as separate imports. Register them once before creating pickers. You need WheelPlugin for the wheel and compact-wheel modes.
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";
PluginRegistry.register(RangePlugin);
PluginRegistry.register(TimezonePlugin);
PluginRegistry.register(WheelPlugin);
new TimepickerUI(input, { range: { enabled: true } });
Upgrading
From 4.5 nothing breaks. 4.6 adds ui.inline.embedded, range.linked, clock.isTimeDisabled with refreshDisabledTime(), the setRange() / getRange() / setActivePart() methods, and a remover returned by on() / once(). See the CHANGELOG.
From 4.4, 4.5 adds optional seconds (clock.showSeconds) and fixes many bugs. It also renames many options to clearer names, for example ui.initialView instead of ui.mobile, and adds setOptions() in place of update(). The old names keep working until 5.0, and the options page lists every pair. A few changes can affect an existing setup: the AM/PM part of the value is always AM / PM whatever labels.am / labels.pm say, out-of-range clock.disabledTime.ranges edges such as 25:00 are reported as errors, the old .tp-ui-ripple classes are gone, and some focus-ring and radius tokens changed. The CHANGELOG lists them under "Check before upgrading".
From 4.3 nothing breaks. 4.4 adds the blueprint and blueprint-dark themes, 16 new labels.* for screen-reader text, and Home/End/PageUp/PageDown keys. See the CHANGELOG.
From 3.x, options are now grouped (clock, ui, labels, ...), events go through picker.on(), and CSS class names changed. There's a migration guide.
From 2.x, you have to import the CSS yourself and some event names changed (legacy guide).
Frameworks
React:
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's also a React wrapper (docs). Vue and Angular examples are in the quick start docs.
Need a date picker?
datepicker-ui is the companion calendar from the same author. It has no dependencies, follows Material Design 3, is safe to import during server rendering and uses the same theme names, so the two pickers match. It covers single dates, ranges and multiple dates.
npm install @pglejzer/datepicker-ui
The date and time guide shows how to use both pickers in one form.
Coming in 5.0
5.0.0 is planned but has no release date yet. 4.5.0 prepares for it. Planned changes:
- Plugins will be built and registered differently.
PluginRegistry.register(...)and the exported plugin types (PluginRegistry,Plugin,PluginManager,PluginFactory,TemplateProvider,ClearHandler,CoreState,EventEmitter) will change. The built-in range, timezone and wheel plugins stay. A migration guide ships with 5.0. - CSS class names keep the
tp-ui-prefix, but the part after it is reworked to follow Material Design component naming. Custom CSS that targets these classes will need updating. 5.0 ships a table of old and new names. - The options renamed in 4.5.0 lose their old names, for example
ui.mobile(nowui.initialView) andclock.smoothHourSnap(nowclock.discreteHourJump). The options page lists every pair. Theselect:am/select:pmevents go too (useselect:period), and so doesupdate()(usesetOptions()). behavior.returnFocusbecomestrueby default: on close, focus goes back to the input, or to thedata-openelement that opened the picker. Setbehavior.returnFocus: truenow to return focus to the input.- Code that is no longer needed is removed, including backward-compatibility paths for the deprecated items and dead code. This reduces bundle size and load cost for the core and each plugin. No size target is set yet. 4.5.0 already removes internal dead code that has no effect on the public API or markup. 5.0 removes the rest together with the items below.
- Items deprecated in 4.5.0 are removed:
OptionTypes(useTimepickerOptions)clock.currentTime.localesgetValue().degreesHoursandgetValue().degreesMinuteslabels.toggleLabel,labels.format24Label,labels.timezoneSelectorLabel- the legacy
focusInputAfterCloseModaloption (usebehavior.returnFocus) - the
hideevent andHideEventData(never emitted) - the
range:get-disabled-timeandrange:update-disabledevents (never emitted) ErrorEventData.rejectedHourandrejectedMinute(alwaysundefined)
- Also removed, because they do nothing or mislead:
- internal events in the public
TimepickerEventMap(range:minute:commit,animation:*) and its catch-all index signature, soon('typo')becomes a type error behavior.delayHandler(it only delays closing and never debounced buttons)TimepickerUI.isAvailable()(same asdocument.querySelector(...) !== null)- the undocumented DOM getters
hour,minutes,okButton,cancelButton,clockHand - the callback arguments of
open(cb)anddestroy()in both forms,destroy(cb)anddestroy({ callback })(useonce('open')or run the code after the call) RangeManager,TimezoneManagerandWheelManagerexported from the plugin entry points (you can't get an instance today, and the new plugin API replaces them)
- internal events in the public
- Changed events and options:
select:hour,select:minuteandselect:secondfire once when a value of their unit is picked, in every mode. Today, in clock mode, they fire when a field is activated, so picking an hour firesselect:minute. To follow face changes, useswitch:face, which 4.5.0 already emitsui.footerreplacesui.inline.showButtonsandwheel.hideFooter:falsehides OK, Cancel and Clear in every mode, andtrueshows them in inline mode toobehavior.commitOnChangereplacesui.inline.autoUpdateandwheel.commitOnScroll: each picked value is written to the input and emitsconfirm, and the picker stays openwheel.ignoreOutsideClickmoves tobehavior.ignoreOutsideClickand works in every mode, the clock modal included- the minute announcement reads
labels.minute, so its default changes fromMinutestoMinute ui.cssClassis applied to one element instead of two
- Package and CSS:
- the plugins no longer ship UMD builds (the UMD core never exposed
PluginRegistry, so they could not be registered from a<script>tag). The core UMD build stays - about 50 CSS custom properties that no rule reads are removed, for example
--tp-tertiary-container,--tp-on-tertiary-container,--tp-radius,--tp-hover-bgand 18 of the 34--tp-size-*tokens. 5.0 lists them all - the
./plugins/*wildcard inpackage.jsonexportsis removed, while./plugins/range,./plugins/timezoneand./plugins/wheelstay crane-straightbecomes a variant ofcranewith square corners (option name not decided)ui.keyboardIcon/ui.clockIcontake an element or a factory function instead of an HTML string
- the plugins no longer ship UMD builds (the UMD core never exposed
Details are on the roadmap.
Development
See app/README.md.
Contributing
Issues and PRs welcome on GitHub.
License
MIT © Piotr Glejzer
Related Packages
Dependency-free, accessible, framework-agnostic clock-face time picker and Web C...
Provides access to native geocoding APIs for NativeScript apps (android.location...
This is the description for the plugin seed. This is what will be published to N...
This is the description for the plugin seed. This is what will be published to N...
Version History
| Version | Released | License |
|---|---|---|
| 4.6.0 | MIT | |
| 4.5.0 | MIT | |
| 4.4.0 | MIT | |
| 4.3.0 | MIT | |
| 4.2.2 | MIT | |
| 4.2.1 | MIT | |
| 4.2.0 | MIT | |
| 4.1.7 | MIT | |
| 4.1.6 | MIT | |
| 4.1.5 | MIT | |
| 4.1.4 | MIT | |
| 4.1.3 | MIT | |
| 4.1.2 | MIT | |
| 4.1.1 | MIT | |
| 4.1.0 | MIT | |
| 4.0.3 | MIT | |
| 4.0.2 | MIT | |
| 4.0.1 | MIT | |
| 4.0.0 | MIT | |
| 3.2.0 | MIT | |
| 3.1.3 | MIT | |
| 3.1.2 | MIT | |
| 3.1.1 | MIT | |
| 3.1.0 | MIT | |
| 3.0.1 | MIT | |
| 3.0.0 | MIT | |
| 2.6.1 | MIT | |
| 2.6.0 | MIT | |
| 2.5.0 | MIT | |
| 2.4.5 | MIT |
Showing the latest 30 of 49. See every release on npm