@19h47/spinbutton
A spinbutton is an input widget that restricts its value to a set or range of discrete values. It provides an accessible, keyboard-navigable interface for numerical input that maintains value constraints, internationalizes aria-valuetext with Intl.PluralRules, and emits events when values change.
Installation
pnpm add @19h47/spinbutton
Also works with npm / yarn / bun.
Manual Download
You can also download the component directly from the GitHub repository at https://github.com/19h47/19h47-spinbutton and include the distributed files in your project.
DOM structure
The Spinbutton requires a specific HTML structure to function properly:
The spinbutton is a div element with the role of spinbutton. It contains two buttons for increasing and decreasing the value, and an input field for displaying the current value.
<div role="spinbutton" aria-valuemin="0" aria-valuemax="100" aria-valuenow="10">
<button class="js-decrease" tabindex="-1" aria-label="decrease" type="button">-</button>
<input type="text" />
<button class="js-increase" tabindex="-1" aria-label="increase" type="button">+</button>
<div class="visually-hidden" aria-live="polite" aria-atomic="true"></div>
</div>
The live region is optional. If it is in the markup, the script updates its text; it is never created or styled by JavaScript.
JavaScript
Here is a simple example of how to use the spinbutton.
import Spinbutton from '@19h47/spinbutton';
const $element = document.querySelector('[role="spinbutton"]');
const spinbutton = new Spinbutton($element);
spinbutton.init();
Event Handling
The Spinbutton component emits a Spinbutton.change event when the value changes:
| Event | Args | Description |
|---|---|---|
| Spinbutton.change | value | Return the current activate value |
Here is an example of how to use the Spinbutton.change event.
import Spinbutton from '@19h47/spinbutton';
const $element = document.querySelector('[role="spinbutton"]');
const spinbutton = new Spinbutton($element);
spinbutton.init();
spinbutton.addEventListener('Spinbutton.change', event => {
console.log(event.detail.value); // Return the current activate value
});
Configuration Options
The spinbutton accepts the following options. They can be set as data attributes on the div or passed when creating the instance.
| Option | Type | Default | Attributes | |
|---|---|---|---|---|
step |
number | The step value to increase or decrease the value. | 1 | data-spinbutton-step |
delay |
number | The delay in milliseconds before the event is triggered. | 20 | data-spinbutton-delay |
Example with options:
import Spinbutton from '@19h47/spinbutton';
const $element = document.querySelector('[role="spinbutton"]');
const spinbutton = new Spinbutton($element, { step: 5, delay: 100 });
spinbutton.init();
Keyboard Support
The spin buttons provide the following keyboard support described in the spinbutton design pattern.
| Key | Function |
|---|---|
| Down Arrow | Decreases value 1 (or step option if given) step. |
| Up Arrow | Increases the value 1 (or step option if given) step. |
| Page Down | Decreases the value 5 (or step option multiply by 5 if given) steps. |
| Page Up | Increases the value 5 (or step option multiply by 5 if given) steps. |
| Home | Decreases to mimimum value (if given). |
| End | Increases to maximum value (if given). |
Role, Property, State, and Tabindex Attributes
The spinbutton uses the following ARIA roles, properties, and states. The aria-valuenow attribute is updated when the value changes. The aria-valuemin and aria-valuemax attributes are used to define the minimum and maximum values of the spinbutton.
| Role | Attribute | Element | Usage |
|---|---|---|---|
spinbutton |
div |
Identifies the div element as a spinbutton. |
|
spinbutton |
aria-valuemin |
div |
The minimum value of the spinbutton. |
spinbutton |
aria-valuemax |
div |
The maximum value of the spinbutton. |
spinbutton |
aria-valuenow |
div |
The current value of the spinbutton. |
spinbutton |
aria-valuetext |
div |
Spoken value, from lang + data-spinbutton-text. |
Customization Examples
Step Size
You can customize the step size:
<div role="spinbutton" data-spinbutton-step="5" aria-valuemin="5" aria-valuemax="50" aria-valuenow="5">
<!-- buttons and input -->
</div>
Dynamic Control
You can dynamically change the minimum, maximum, and current values:
// Set new minimum value
spinbutton.setMin(10);
// Set new maximum value
spinbutton.setMax(200);
// Set new value
spinbutton.setValue(50);
Internationalization
aria-valuetext is formatted with Intl.PluralRules. Nothing is hardcoded in English: the locale is the closest lang attribute (<html lang="fr">, or lang on the spinbutton), and the words come from data-spinbutton-text.
Without that attribute, the value text is just the number (0, 1, 2).
Keys are CLDR plural categories: one, other, and zero / two / few / many when the language needs them.
English
0 and 2+ select other. 1 selects one.
<div lang="en" role="spinbutton" data-spinbutton-text='{"one":"barrel","other":"barrels"}' aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
<!-- buttons and input -->
</div>
aria-valuenow |
aria-valuetext |
|---|---|
0 |
0 barrels |
1 |
1 barrel |
2 |
2 barrels |
French
0 and 1 select one. 2+ selects other.
<div lang="fr" role="spinbutton" data-spinbutton-text='{"one":"tonneau","other":"tonneaux"}' aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
<!-- buttons and input -->
</div>
aria-valuenow |
aria-valuetext |
|---|---|
0 |
0 tonneau |
1 |
1 tonneau |
2 |
2 tonneaux |