Ranges and steppers
tier 2 — classesThe controls whose native internals have to be named per engine. Everything here restates one design in several vendor pseudo-elements.
::-webkit-…, ::-moz-… list applies in neither browser. Write one rule per engine.
Range
appearance: none removes the whole native widget, track included, so both are
drawn here.
A range gives no readout. Put the value in text beside it with
.form-range-value — the thumb position is not a number, and it is unreadable to a
screen reader on its own. The readout is fixed-width and tabular, so the track does not shift
while dragging.
Reservations below this are held for a human decision.
<div class="form-field" style="max-width:420px">
<label class="form-label" for="r-conf">Confidence threshold</label>
<div class="dr-row">
<input class="form-range" id="r-conf" type="range" min="0" max="100" value="85" />
<span class="form-range-value">85%</span>
</div>
<p class="form-hint">Reservations below this are held for a human decision.</p>
</div>
Stepper
For a value adjusted one at a time. The native spinner is tiny, inconsistent between engines, and invisible until hover in some — so it is removed and replaced with two real buttons, which are also a usable size on touch.
Give each button an aria-label: + and − are
announced as “plus” and “minus” without saying what of. Hiding the
native spinner needs both the Firefox property and the WebKit pseudo-element; neither implies
the other.
<div class="form-field" style="max-width:220px">
<label class="form-label" for="s-retry">Retry attempts</label>
<div class="stepper">
<button class="btn" type="button" aria-label="One fewer retry">−</button>
<input class="form-input" id="s-retry" type="number" value="3" min="0" max="9" />
<button class="btn" type="button" aria-label="One more retry">+</button>
</div>
</div>
Date and time
.form-input already covers the box. What needs naming is the calendar picker
glyph, which the browser draws in a fixed colour we cannot reach — so it is inverted, by an
amount that moves with the theme (--picker-invert).
The picker panel is dark because color-scheme is set on
<html> from --color-scheme. That is also what makes a
<select>'s option list and the native scrollbars match the theme.
<div class="field-grid" style="max-width:520px">
<div class="form-field">
<label class="form-label" for="d-from">Active from</label>
<input class="form-input" id="d-from" type="date" value="2026-07-31" />
</div>
<div class="form-field">
<label class="form-label" for="d-at">At</label>
<input class="form-input" id="d-at" type="time" value="18:00" />
</div>
</div>
Three sizes
The stepper takes its tier from its children: .btn-sm on both buttons and
.form-input-sm on the input. A date or time input is a
.form-input, so it takes the tier the same way.
Every row here is one control height, so the stepper, the date field and the button beside them agree at each size. Mixing tiers within a row is the one thing that makes a filter bar look assembled by accident.
<div class="dr-col dr-gap-2">
<div class="dr-row-wrap">
<div class="stepper">
<button class="btn btn-sm" type="button" aria-label="One fewer retry">−</button>
<input class="form-input form-input-sm" type="number" value="3" min="0" max="9" aria-label="Retry attempts, small" />
<button class="btn btn-sm" type="button" aria-label="One more retry">+</button>
</div>
<input class="form-input form-input-sm" type="date" value="2026-07-31" aria-label="Active from, small" />
<button class="btn btn-sm" type="button">Apply</button>
</div>
<div class="dr-row-wrap">
<div class="stepper">
<button class="btn" type="button" aria-label="One fewer retry">−</button>
<input class="form-input" type="number" value="3" min="0" max="9" aria-label="Retry attempts" />
<button class="btn" type="button" aria-label="One more retry">+</button>
</div>
<input class="form-input" type="date" value="2026-07-31" aria-label="Active from" />
<button class="btn" type="button">Apply</button>
</div>
<div class="dr-row-wrap">
<div class="stepper">
<button class="btn btn-lg" type="button" aria-label="One fewer retry">−</button>
<input class="form-input form-input-lg" type="number" value="3" min="0" max="9" aria-label="Retry attempts, large" />
<button class="btn btn-lg" type="button" aria-label="One more retry">+</button>
</div>
<input class="form-input form-input-lg" type="date" value="2026-07-31" aria-label="Active from, large" />
<button class="btn btn-lg" type="button">Apply</button>
</div>
</div>