Minimal
Wrap a native <input type="number"> with wf-spinner and add data-wf-spinner . The component auto-inits and injects the +/− buttons.
<div class="wf-spinner" data-wf-spinner>
<input type="number" min="1" max="99" value="1" name="qty">
</div>
A progressive enhancement of the native <input type="number"> . Adds styled +/− stepper buttons with hold-to-spin, optional prefix/suffix decorations, min/max clamping, step-snapping, and decimal precision control. Add data-wf-spinner to any .wf-spinner wrapper and the component auto-initializes on DOMContentLoaded .
Wrap a native <input type="number"> with wf-spinner and add data-wf-spinner . The component auto-inits and injects the +/− buttons.
<div class="wf-spinner" data-wf-spinner>
<input type="number" min="1" max="99" value="1" name="qty">
</div>
Place the spinner inside a wf-field to get a label, helper text, and correct focus-ring inheritance.
<div class="wf-field">
<label>Quantity</label>
<div class="wf-spinner" data-wf-spinner>
<input type="number" min="1" max="999" value="1" name="qty">
</div>
</div>
Decorative text is displayed before or after the input. Set via data-wf-prefix or data-wf-suffix . Both can be used together.
Currency or unit prefix. Decimal step on the input auto-detects precision.
<!-- Decimal step auto-detects 2 decimal places -->
<div class="wf-spinner" data-wf-spinner data-wf-prefix="$">
<input type="number" min="0" max="9999" step="0.01" value="9.99" name="price">
</div>
Unit suffix after the value. Use data-wf-precision to override decimal display when the unit implies it.
<!-- Suffix with explicit precision -->
<div class="wf-spinner" data-wf-spinner data-wf-suffix="kg" data-wf-precision="1">
<input type="number" min="0" max="300" step="0.5" value="70.0" name="weight">
</div>
<!-- Prefix + Suffix combined -->
<div class="wf-spinner" data-wf-spinner data-wf-prefix="$" data-wf-suffix="/ hr">
<input type="number" min="0" step="0.5" value="25.00" name="rate">
</div>
Three size presets set via data-wf-size . The default (medium) needs no attribute. Sizes apply to the input padding, button padding, and prefix/suffix font size.
Use data-wf-size="sm" or data-wf-size="lg" . Omit the attribute for the default medium size.
<div class="wf-spinner" data-wf-spinner data-wf-size="sm">
<input type="number" min="1" max="99" value="1" name="qty">
</div>
<div class="wf-spinner" data-wf-spinner>
<input type="number" min="1" max="99" value="1" name="qty">
</div>
<div class="wf-spinner" data-wf-spinner data-wf-size="lg">
<input type="number" min="1" max="99" value="1" name="qty">
</div>
Applies 55% opacity and removes all pointer events. Mirrors the state to the native input.
<div class="wf-spinner" data-wf-spinner data-wf-disabled>
<input type="number" min="1" max="99" value="5" name="qty">
</div>
<!-- Or disable via the native input -->
<div class="wf-spinner" data-wf-spinner>
<input type="number" value="5" name="qty" disabled>
</div>
Input background becomes subtle and steppers are pointer-events off — values are visible and selectable but cannot be changed.
<div class="wf-spinner" data-wf-spinner data-wf-readonly>
<input type="number" value="42" name="total">
</div>
<!-- Or use the native readonly attribute -->
<div class="wf-spinner" data-wf-spinner>
<input type="number" value="42" name="total" readonly>
</div>
Add data-wf-no-steppers to hide the +/− buttons. The input becomes left-aligned and behaves like a plain styled number field — still supports prefix/suffix and all other options.
Useful for large ranges or amounts where the buttons provide no real benefit. All validation and precision formatting still apply.
<div class="wf-spinner" data-wf-spinner data-wf-no-steppers data-wf-prefix="$">
<input type="number" min="0" step="0.01" value="1250.00" name="amount">
</div>
| Attribute | Element | Description |
|---|---|---|
| data-wf-spinner | .wf-spinner | Required. Marks the wrapper for auto-init on DOMContentLoaded . |
| data-wf-prefix="$" | .wf-spinner | Decorative text displayed before the input (rendered as wf-spinner-prefix ). |
| data-wf-suffix="kg" | .wf-spinner | Decorative text displayed after the input (rendered as wf-spinner-suffix ). |
| data-wf-precision="N" | .wf-spinner | Number of decimal places to display. Default: auto-detected from the input's step attribute. |
| data-wf-no-steppers | .wf-spinner | Hide the +/− buttons. Input becomes left-aligned. |
| data-wf-size="sm|lg" | .wf-spinner | Size preset. Omit for the default medium size. |
| data-wf-disabled | .wf-spinner | Disables the control and mirrors the state to the native input. Also triggered by disabled on the <input> . |
| data-wf-readonly | .wf-spinner | Makes the control read-only. Also triggered by readonly on the <input> . |
| data-wf-i18n-increment="…" | .wf-spinner | Per-instance aria-label override for the + button. Default: "Increase" . |
| data-wf-i18n-decrement="…" | .wf-spinner | Per-instance aria-label override for the − button. Default: "Decrease" . |
| min, max, step, value, name | input | Standard <input type="number"> attributes — all respected by the component for clamping, stepping, and precision detection. |
| Property | Default | Description |
|---|---|---|
| --wf-sp-radius | var(--wf-radius-3) | Border radius of the wrapper. |
| --wf-sp-border | var(--wf-border) | Border color. Also used for the separator lines between buttons, prefix, and the input. |
| --wf-sp-surface | var(--wf-surface-subtle) | Background of the stepper buttons, prefix, and suffix areas. |
All events are dispatched on the .wf-spinner wrapper and bubble up.
| Event | detail | Description |
|---|---|---|
| wf:spinner-change | { value } | Fires when a value is committed: on blur after typing, or each time a stepper button is clicked or held. detail.value is the clamped, formatted number. |
| wf:spinner-input | { value } | Fires on every keystroke while the user types. Value may be unclamped (still within the browser's live parse of the field). |
import { WojoSpinner } from './src/js/components/wf-spinner.js'
// Manual init
const sp = new WojoSpinner(document.querySelector('[data-wf-spinner]')).init()
// Public API
sp.getValue() // → number: current value
sp.setValue(42) // Set value (clamped to min/max, formatted)
sp.stepUp() // Increment by one step
sp.stepDown() // Decrement by one step
sp.setDisabled(true) // Enable/disable the component
sp.setReadonly(true) // Toggle read-only mode
sp.destroy() // Remove all event listeners
// Instance reference (auto-init sets this)
const sp2 = document.querySelector('[data-wf-spinner]')._wfSpinner
// Events — dispatched on the .wf-spinner element
document.querySelector('[data-wf-spinner]').addEventListener('wf:spinner-change', e => {
console.log('Committed value:', e.detail.value)
})
document.querySelector('[data-wf-spinner]').addEventListener('wf:spinner-input', e => {
console.log('Live value:', e.detail.value)
})
All modifier classes available on .wf-spinner
| Class | Type | Description |
|---|---|---|
| wf-spinner | Base | Wrapper element. Wraps the native <input type="number"> . Requires data-wf-spinner for JS init. |
| wf-spinner-btn / -dec / -inc | Child | Stepper buttons injected by JS. -dec is the minus button; -inc is the plus button. |
| wf-spinner-prefix | Child | Decorative text is displayed before the input. Injected by JS from data-wf-prefix . |
| wf-spinner-suffix | Child | Decorative text is displayed after the input. Injected by JS from data-wf-suffix . |