Components / Form & Input / Stepper
Stepper beta
A number with minus and plus on either side, cast as one control. Bounds are enforced, not suggested.
<div style="display:flex;gap:26px;flex-wrap:wrap;align-items:center">
<label style="display:grid;gap:6px;font-size:12.5px;font-weight:550">Seats
<span class="k-stepper" data-stepper data-min="1" data-max="8">
<button type="button" data-step-btn="-1" aria-label="Decrease seats">−</button>
<output data-step-val aria-live="polite">2</output>
<button type="button" data-step-btn="1" aria-label="Increase seats">+</button>
</span>
</label>
<label style="display:grid;gap:6px;font-size:12.5px;font-weight:550">Copies
<span class="k-stepper" data-stepper data-min="1" data-max="5">
<button type="button" data-step-btn="-1" aria-label="Decrease copies">−</button>
<output data-step-val aria-live="polite">5</output>
<button type="button" data-step-btn="1" aria-label="Increase copies">+</button>
</span>
</label>
</div>/* KATA Stepper */
.k-stepper{display:inline-flex;align-items:stretch;height:38px;border:1px solid var(--k-line);border-radius:var(--k-radius);background:var(--k-bg);overflow:hidden}
.k-stepper button{width:37px;border:0;background:transparent;color:var(--k-ink);font-family:var(--k-font);font-size:16px;cursor:pointer;transition:background .13s}
.k-stepper button:hover:not(:disabled){background:var(--k-bg-soft)}
.k-stepper button:disabled{opacity:.35;cursor:not-allowed}
.k-stepper button:focus-visible{outline:none;box-shadow:inset var(--k-ring)}
.k-stepper output{display:flex;align-items:center;justify-content:center;min-width:46px;padding:0 6px;border-left:1px solid var(--k-line);border-right:1px solid var(--k-line);font-family:var(--k-mono);font-size:13.5px;color:var(--k-ink)}
// KATA Stepper — bounded increment control.
document.querySelectorAll('[data-stepper]').forEach(function (root) {
var out = root.querySelector('[data-step-val]');
var min = parseFloat(root.dataset.min || '-Infinity');
var max = parseFloat(root.dataset.max || 'Infinity');
var step = parseFloat(root.dataset.step || '1');
function render() {
var v = parseFloat(out.textContent) || 0;
root.querySelectorAll('[data-step-btn]').forEach(function (b) {
var d = parseFloat(b.dataset.stepBtn);
b.disabled = (d < 0 && v <= min) || (d > 0 && v >= max);
});
}
root.querySelectorAll('[data-step-btn]').forEach(function (b) {
b.addEventListener('click', function () {
var v = parseFloat(out.textContent) || 0;
v = Math.min(max, Math.max(min, v + parseFloat(b.dataset.stepBtn) * step));
out.textContent = String(v);
render();
});
});
render();
});
Usage
For small integer quantities — seats, copies, nights. For typing a number directly, use Input type=number instead.
Accessibility
Real buttons with aria-labels; the value is an <output> with aria-live so changes are announced. Buttons disable at the bounds.