Components / Form & Input / Time Picker
Time Picker stable
The Date Picker’s other half: an input with two scrolling columns of hours and minutes.
Open it, pick an hour and a minute — or just type. The panel closes on Escape or a click away.
Uses: Input — include its CSS alongside this one.
<div class="k-tp" data-tp style="max-width:220px">
<div class="wrap-in">
<input class="input" data-tp-input type="text" value="14:30" placeholder="HH:MM" aria-label="Time" style="width:100%;padding-right:42px">
<button class="k-tp-btn" data-tp-btn type="button" aria-label="Open time picker" aria-expanded="false"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="square" stroke-linejoin="bevel" aria-hidden="true"><circle cx="12" cy="12" r="8.5"/><path d="M12 7.5V12l3 2"/></svg></button>
</div>
<div class="k-tp-panel" data-tp-panel role="dialog" aria-label="Choose a time" hidden>
<div class="k-tp-col" data-tp-h><span class="hd">HOUR</span></div>
<div class="k-tp-col" data-tp-m><span class="hd">MIN</span></div>
</div>
</div>
<p style="font-size:12.5px;color:var(--k-ink-3);margin-top:9px">Open it, pick an hour and a minute — or just type. The panel closes on Escape or a click away.</p>/* KATA Time Picker — two columns, one value. */
.k-tp{position:relative;font-family:var(--k-font);color:var(--k-ink)}
.k-tp .wrap-in{position:relative}
.k-tp-btn{position:absolute;right:6px;top:50%;transform:translateY(-50%);width:28px;height:28px;border:0;border-radius:7px;background:none;color:var(--k-ink-3);display:flex;align-items:center;justify-content:center;cursor:pointer}
.k-tp-btn:hover{background:var(--k-bg-soft);color:var(--k-ink)}
.k-tp-panel{position:absolute;inset-inline-start:0;top:calc(100% + 8px);z-index:50;background:var(--k-bg);border:1px solid var(--k-line);border-radius:14px;box-shadow:0 20px 44px -14px rgba(16,24,40,.22);padding:10px;display:flex;gap:6px}
.k-tp-panel[hidden]{display:none}
.k-tp-col{display:flex;flex-direction:column;gap:2px;max-height:196px;overflow:auto;width:64px;padding-inline-end:2px}
.k-tp-col .hd{font-size:10.5px;font-weight:600;letter-spacing:.08em;color:var(--k-ink-3);text-align:center;padding:3px 0 5px;position:sticky;top:0;background:var(--k-bg)}
.k-tp-opt{padding:7px 0;border:0;border-radius:8px;background:none;font-family:inherit;font-size:13.5px;color:var(--k-ink-2);cursor:pointer;text-align:center}
.k-tp-opt:hover{background:var(--k-bg-soft);color:var(--k-ink)}
.k-tp-opt.sel{background:var(--k-ink);color:var(--k-bg);font-weight:600}
// KATA Time Picker — hours and five-minute steps; typing still works.
document.querySelectorAll('[data-tp]').forEach(function (el) {
var input = el.querySelector('[data-tp-input]');
var btn = el.querySelector('[data-tp-btn]');
var panel = el.querySelector('[data-tp-panel]');
var hCol = el.querySelector('[data-tp-h]');
var mCol = el.querySelector('[data-tp-m]');
function pad(n) { return String(n).padStart(2, '0'); }
function build(col, values, cur) {
col.querySelectorAll('.k-tp-opt').forEach(function (o) { o.remove(); });
values.forEach(function (v) {
var b = document.createElement('button');
b.type = 'button'; b.className = 'k-tp-opt' + (v === cur ? ' sel' : ''); b.textContent = v; b.setAttribute('data-v', v);
col.appendChild(b);
});
}
function render() {
var m = /^(\d{1,2}):(\d{2})/.exec(input.value || '');
build(hCol, Array.from({ length: 24 }, function (_, i) { return pad(i); }), m ? pad(+m[1]) : '');
build(mCol, Array.from({ length: 12 }, function (_, i) { return pad(i * 5); }), m ? m[2] : '');
}
function open() { render(); panel.hidden = false; btn.setAttribute('aria-expanded', 'true'); }
function close() { panel.hidden = true; btn.setAttribute('aria-expanded', 'false'); }
btn.addEventListener('click', function () { panel.hidden ? open() : close(); });
input.addEventListener('focus', open);
panel.addEventListener('click', function (e) {
var b = e.target.closest('.k-tp-opt'); if (!b) return;
var m = /^(\d{1,2}):(\d{2})/.exec(input.value || '');
var h = m ? pad(+m[1]) : '09', min = m ? m[2] : '00';
if (b.parentElement === hCol) h = b.getAttribute('data-v'); else min = b.getAttribute('data-v');
input.value = h + ':' + min;
input.dispatchEvent(new Event('change', { bubbles: true }));
render();
});
document.addEventListener('click', function (e) { if (!el.contains(e.target)) close(); });
document.addEventListener('keydown', function (e) { if (e.key === 'Escape') close(); });
render();
});
Installation
CLI
node kata-ui.mjs add time-picker
Writes the part’s CSS and JS into your project. Dependencies are resolved and written with it. New to the CLI? See the CLI doc.
Manual
- Load
tokens.cssonce, before any part. - Copy the CSS tab into your stylesheet.
- Copy the HTML where it belongs. The part has a script — include it after the markup; it initializes itself.
Usage
Minutes step by five, the way appointments actually work; type the value directly when you know it — the panel is a courtesy, not a gate. The pick writes HH:MM (24-hour) into the input.
Accessibility
The columns are lists of buttons with plain labels ("14", "05") under hour and minute headings; the input stays editable by keyboard, Escape closes the panel, and the toggle button is named for what it opens.
Anatomy
The public surface of this part, read from its own source. Class names are the API — the CSS tab above is the full contract.
.k-tp | class |
.wrap-in | class |
.k-tp-btn | class |
.k-tp-panel | class |
.k-tp-col | class |
.hd | class |
.k-tp-opt | class |
.sel | class |
data-tp | data hook |
data-tp-input | data hook |
data-tp-btn | data hook |
data-tp-panel | data hook |
data-tp-h | data hook |
data-tp-m | data hook |
data-v | data hook |
aria-label | ARIA |
aria-expanded | ARIA |
aria-hidden | ARIA |
Related parts
Button stable
Black leads; lines and ghosts support. Hover, disabled and loading states included.
Button Group stable
Buttons joined into one piece: shared edges, one radius, no gaps. For toolbars, paging and split actions.
Calendar stable
One month, standing on its own: a grid of real day buttons with today marked and a selection you can move.
Cascader beta
A select that walks a tree: pick a branch, then a branch of the branch, until the leaf is the answer.