Number stepper
A native <input type="number"> in an
input group flanked by two
.re-input-group__action buttons marked data-re-number-step="1" / "-1".
The input is marked data-re-number. Opt into the behavior:
import { enhanceNumberStepper } from "@relements/core";enhanceNumberStepper(document);The native number input is the spinbutton — the behavior adds nothing to
its semantics, it only gives pointer users bigger step targets. Pressing a
button calls native stepUp()/stepDown() and then dispatches input +
change (which those methods don’t fire), so frameworks observe the change
like typing. min/max/step, arrow keys, and form value stay native; the
buttons are tabindex="-1" and disable at the matching bound and when the input
is disabled or read-only. Without JavaScript it’s a normal number input with its
native spinner.
<label class="re-field">
<span class="re-field__label">Quantity</span>
<span class="re-input-group">
<button
class="re-input-group__action"
type="button"
data-re-number-step="-1"
aria-label="Decrease"
tabindex="-1"
>
−
</button>
<input
class="re-input"
type="number"
data-re-number
value="1"
min="0"
max="10"
step="1"
aria-label="Quantity"
/>
<button
class="re-input-group__action"
type="button"
data-re-number-step="1"
aria-label="Increase"
tabindex="-1"
>
+
</button>
</span>
</label> Accessibility
Section titled “Accessibility”- Keyboard — All input lives on the native
<input type="number">, the only tab stop: Tab to focus, then type, or use Arrow Up / Arrow Down to step bystep(the browser also honours min/max here). The ± buttons aretabindex="-1"and deliberately not in the tab order — they’re pointer shortcuts for the same native stepping that the arrow keys already provide. - Focus — The input group owns the
visible
:focus-withinring, so the whole control lights up when the input is focused; the input’s own ring is suppressed to avoid a double outline. A ± button reached by pointer/script shows an inset:focus-visiblering. - Semantics —
<input type="number">is an implicitrole="spinbutton", and the behavior adds nothing to it —min/max/step, the current value, and validity stay 100% native, announced by assistive tech as a spin button. Each ± button is a<button type="button">with anaria-label(Decrease/Increase) since its glyph (− / +) isn’t reliable accessible text. Buttons setdisabledat the matching bound and when the input is disabled or read-only, so AT and pointer users can’t step past a limit; the field’saria-invalid/:user-invalidstate is reflected on the group. - Notes — Pressing a button calls native
stepUp()/stepDown()and then dispatchesinput+change(which those methods don’t fire on their own), so screen-reader value announcements and framework bindings update exactly as if the user typed. Without JavaScript it degrades to a plain number input with its native spinner — fully operable by keyboard. See the accessibility guide for cross-component conventions.