Feedback Spinner

Spinner

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 .

Basic Spinner

Minimal

Wrap a native <input type="number"> with wf-spinner and add data-wf-spinner . The component auto-inits and injects the +/− buttons.

HTML
<div class="wf-spinner" data-wf-spinner>
  <input type="number" min="1" max="99" value="1" name="qty">
</div>

Inside a Form Field

Place the spinner inside a wf-field to get a label, helper text, and correct focus-ring inheritance.

Enter a value between 0 and 100.
HTML
<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>

Prefix & Suffix

Decorative text is displayed before or after the input. Set via data-wf-prefix or data-wf-suffix . Both can be used together.

Prefix

Currency or unit prefix. Decimal step on the input auto-detects precision.

HTML
<!-- 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>

Suffix

Unit suffix after the value. Use data-wf-precision to override decimal display when the unit implies it.

HTML
<!-- 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>

Sizes

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.

sm / default / lg

Use data-wf-size="sm" or data-wf-size="lg" . Omit the attribute for the default medium size.

HTML
<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>

States

Disabled data-wf-disabled

Applies 55% opacity and removes all pointer events. Mirrors the state to the native input.

HTML
<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>

Readonly data-wf-readonly

Input background becomes subtle and steppers are pointer-events off — values are visible and selectable but cannot be changed.

HTML
<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>

Without Steppers

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.

No Steppers data-wf-no-steppers

Useful for large ranges or amounts where the buttons provide no real benefit. All validation and precision formatting still apply.

HTML
<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>

Data Attributes

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.

Custom Properties

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.

Events & Public API

Events

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).
JavaScript
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)
})

Class Reference

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 .