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. Date and time inputs, the other such family, have a page of their own.
::-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="sedna-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>
Three sizes
The stepper takes its tier from its children: .btn-sm on both buttons and
.form-input-sm on the input.
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="sedna-col sedna-gap-2">
<div class="sedna-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="sedna-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="sedna-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>