Forms & Input

Spinbutton

Available

A numeric input that increments and decrements via Up/Down keys or buttons, with range announcements.

What is a spin button?

A spin button is a UI control that lets users increment or decrement a value one step at a time using + / − (or ↑ / ↓), selecting a number within a given range. It is commonly used for specifying quantities, counts, or floor numbers.

It is similar to a slider, but a spin button excels at selecting precise, discrete values. The information it needs to convey is the same: the current value, the range (minimum and maximum), and keyboard interaction.

Why does accessibility matter?

The easiest way to meet all of these requirements is to use the native <input type="number">. The browser provides the increment/decrement buttons, keyboard interaction, and screen reader announcements automatically.

Live demo (recommended implementation)

Below is a native <input type="number">.Without using a mouse, focus the input and try changing the value with and .

Accessible spin button (native number input)

Try it: Tab to the input → ↑ ↓ to increment/decrement by 1, or type a number directly. Range: 1–10.

Tip

When a screen reader focuses the input, it announces something like "Quantity, spin button, 1," conveying the role and current value. As you change the value with ↑↓, the new value is announced. All of this is provided automatically by the native element.

Keyboard interaction

KeyActionPriority
Increase the value by one stepRequired
Decrease the value by one stepRequired
Home / EndSet to minimum / maximum valueRecommended
Number keysEnter a value directlyOptional

Note

Incrementing/decrementing with and direct input are automatic with <input type="number">. For custom implementations (role="spinbutton"), you need to handle ↑↓ yourself via a keydown listener.

Required WAI-ARIA roles and properties

TargetAttribute / RoleMeaning
Native number input<label for> associationGive the input a label. Role, value, range, and increment/decrement are handled automatically.
Custom spinbutton elementrole="spinbutton"Convey to assistive technologies that this is a spinbutton.
Custom spinbutton elementtabindex="0"Make the element keyboard-focusable so it can receive Up/Down arrow key events.
Custom spinbutton elementaria-valuemin / aria-valuemaxDefines the selectable range (minimum and maximum).
Custom spinbutton elementaria-valuenowCurrent value. Must be updated on every increment or decrement.
Custom spinbutton elementaria-label / aria-labelledbyA label describing what value the spinbutton controls.
Increment/decrement buttons<button> + aria-label="Increase/Decrease"Use real button elements for + and -; add a label if using icon-only buttons.

Implementation: recommended pattern (Good)

Good / Recommended

Start with <input type="number">. Just associating a label gives you the role, value, range, and keyboard interaction for free.

Markup:

<label for="qty">Quantity</label>
<input
  type="number"
  id="qty"
  name="qty"
  min="1"
  max="10"
  step="1"
  value="1" />

Only when design requirements absolutely prevent using the native element should you build a custom one with role="spinbutton". Make the + and − real <button> elements, and handle ↑↓ on the spinbutton itself.

<!-- Only build a custom one when native doesn't meet design requirements -->
<span id="ppl-label">Number of adults</span>
<div class="spin">
  <button type="button" data-dec aria-label="Decrease">−</button>
  <div role="spinbutton"
       tabindex="0"
       aria-labelledby="ppl-label"
       aria-valuemin="1"
       aria-valuemax="9"
       aria-valuenow="2"
       id="ppl-spin">2</div>
  <button type="button" data-inc aria-label="Increase">+</button>
</div>
const spin = document.getElementById('ppl-spin');
const MIN = 1, MAX = 9, STEP = 1;

function setValue(next) {
  const v = Math.min(MAX, Math.max(MIN, next));
  spin.setAttribute('aria-valuenow', String(v));
  spin.textContent = String(v); // Keep the visual display in sync
}

document.querySelector('[data-inc]').addEventListener('click', () =>
  setValue(Number(spin.getAttribute('aria-valuenow')) + STEP)
);
document.querySelector('[data-dec]').addEventListener('click', () =>
  setValue(Number(spin.getAttribute('aria-valuenow')) - STEP)
);

// Allow incrementing/decrementing with ↑↓ when the spinbutton itself has focus
spin.addEventListener('keydown', (e) => {
  const now = Number(spin.getAttribute('aria-valuenow'));
  if (e.key === 'ArrowUp')   { e.preventDefault(); setValue(now + STEP); }
  if (e.key === 'ArrowDown') { e.preventDefault(); setValue(now - STEP); }
  if (e.key === 'Home') { e.preventDefault(); setValue(MIN); }
  if (e.key === 'End')  { e.preventDefault(); setValue(MAX); }
});

Anti-pattern (Bad)

Below is a spin button built with a text input and <div>-based + and − buttons.You can click them with a mouse, but you cannot increment/decrement with the keyboard, and neither the value nor the range is announced.

Broken spin button built with text input + div buttons
Quantity
+

Try it: Pressing Tab does not focus the + or − buttons. A screen reader cannot identify this as a 'spin button' or announce the current value.

<!-- ❌ Anti-pattern: text input + div-based + and − buttons -->
<span>Quantity</span>
<div class="spin">
  <div class="btn" onclick="dec()">−</div>   <!-- div: not focusable -->
  <input type="text" id="qty" value="1">      <!-- No label, not a number input -->
  <div class="btn" onclick="inc()">+</div>
</div>
<!--
  No role="spinbutton" or aria-valuenow/min/max.
  The + and − are divs, so they can't be reached by keyboard,
  and the value and range are not announced to assistive technologies.
-->

Bad / Avoid

Problems with this implementation:

  • Cannot increment/decrement via keyboard — The + and − are div elements that cannot receive focus or be activated.
  • Role is not communicated — Without role="spinbutton", it is not recognized as a spin button.
  • Value and range are not communicated — Missing aria-valuenow / valuemin / valuemax.
  • No accessible name — The label is not associated, so the user cannot tell what this number represents.

Implementation checklist


Source (English):Spinbutton Pattern — W3C APG(opens in a new tab)