Stepper Number
Quantity input with custom −/+ controls. Native `<input type="number">` underneath keeps keyboard arrows, validation, and screen-reader semantics intact.
Stepper Number the 14th of 28 designs in the 28 CSS Input Field Designs collection. The design pairs CSS styling with a small amount of JavaScript for interactivity. Copy the HTML, CSS and JavaScript panels below into your project — the JS is self-contained, has zero dependencies, and is safe to drop into any framework (React, Vue, Svelte, plain HTML). The design honours prefers-reduced-motion and uses real semantic markup, so it ships accessibility-ready out of the box.
Live preview
The code
<label class="if-step">
<span class="if-step-label">Quantity</span>
<span class="if-step-wrap">
<button type="button" class="if-step-btn" data-if-step="-1" aria-label="Decrease">−</button>
<input type="number" name="qty" min="0" max="99" value="1" aria-label="Quantity" />
<button type="button" class="if-step-btn" data-if-step="1" aria-label="Increase">+</button>
</span>
</label> .if-step {
display: grid;
gap: 6px;
width: 100%;
max-width: 220px;
font-size: 11px;
color: #b8b6d4;
}
.if-step-label {
font-weight: 600;
}
.if-step-wrap {
display: grid;
grid-template-columns: 36px 1fr 36px;
background: #1a1a22;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 8px;
overflow: hidden;
transition: border-color 0.2s;
}
.if-step-wrap:focus-within {
border-color: #14b8a6;
}
.if-step-btn {
background: rgba(20, 184, 166, 0.06);
border: 0;
color: #5eead4;
font-size: 18px;
font-weight: 700;
cursor: pointer;
transition:
background 0.15s,
color 0.15s;
}
.if-step-btn:hover {
background: rgba(20, 184, 166, 0.18);
color: #fff;
}
.if-step-btn:active {
background: rgba(20, 184, 166, 0.28);
}
.if-step input {
background: transparent;
border: 0;
outline: none;
color: #f0eeff;
font-size: 16px;
font-weight: 600;
text-align: center;
padding: 10px 0;
-moz-appearance: textfield;
}
.if-step input::-webkit-outer-spin-button,
.if-step input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
} // Stepper +/- buttons — clamp to min/max
document.querySelectorAll(".if-step-wrap").forEach(function (wrap) {
var input = wrap.querySelector('input[type="number"]');
if (!input) return;
wrap.querySelectorAll("[data-if-step]").forEach(function (btn) {
btn.addEventListener("click", function () {
var dir = parseInt(btn.dataset.ifStep, 10) || 0;
var min = input.min !== "" ? Number(input.min) : -Infinity;
var max = input.max !== "" ? Number(input.max) : Infinity;
var val = (Number(input.value) || 0) + dir;
if (val < min) val = min;
if (val > max) val = max;
input.value = String(val);
});
});
});