Spin Button
UI pattern
spinbutton role, a descriptive name, and a value within its reported limits, and keyboard users can reach and step it.A spin button is an input that restricts its value to a set or range of discrete values, such as a quantity from 1 to 10 or the minute of an hour from 0 to 59. It usually has three parts: a text field that displays the current value, and increase and decrease buttons beside it.
Users change the value by pressing the arrow keys, by activating the buttons, or, in most builds, by typing a number into the field.
The native HTML <input type="number"> element is a spin button. Browsers give it the spinbutton role, report its value, min, and max, accept typed numbers, and step the value with the arrow keys, all without ARIA or code. A custom spin button built from a generic element carries role="spinbutton" and ARIA value properties instead, and then every key, every button, and every value update becomes your responsibility.
A spin button is one of several range widgets, which report a value within limits. Related Patterns lists the others. A slider also sets a value in a range, but along a track, where the value's position matters more than the exact number. Unlike the other range widgets, a spin button doesn't need a minimum or a maximum.
Note: Some touch-screen assistive technologies can't operate a custom ARIA spin button, because their gestures for raising and lowering a value don't yet produce the key events the spin button listens for. This is one more reason to prefer <input type="number">. If you build a custom spin button, test it with assistive technology on touch devices before you ship it.
When to Use
Use a spin button when:
- Users choose a number that changes in steps, such as a quantity, a number of guests, or a font size.
- Users benefit from both typing an exact value and stepping it up or down.
- The range is too large to offer as a list of options, such as a year between 1900 and 2100.
Do not use a spin button when:
- The value is made of digits but isn't a quantity, such as a postal code, a phone number, or a card number. Use a text input with
inputmode="numeric"instead. - Stepping the value would help no one, and an accidental arrow key press could change it unnoticed. Use a text input instead.
- Users judge the value by its position in a range, such as a volume or a brightness level. Use a slider instead.
- The choices are a short list of named options rather than numbers. Use a radio group, a listbox, or a
<select>element instead.
Examples
Code in this section is meant to demonstrate techniques rather than requirements and lacks features needed in a production application.
Both examples collect the number of tickets for an order, from 1 to 10.
Native HTML Spin Button
The <input type="number"> element needs only its attributes. min and max set the range, step sets how far each arrow key press moves the value, and the <label> gives the spin button its accessible name. The browser draws the buttons, steps the value with the Up Arrow and Down Arrow keys, and accepts typed numbers.
Native spin button HTML
<label for="tickets">
Tickets
</label>
<input
type="number"
id="tickets"
name="tickets"
min="1"
max="10"
step="1"
value="2" />Custom ARIA Spin Button
The spinbutton role goes on the element that displays the value and takes focus. aria-valuemin, aria-valuemax, and aria-valuenow report the range and the current value, aria-labelledby points to the visible label, and tabindex="0" puts the spin button in the tab order. The increase and decrease buttons sit beside it with tabindex="-1", so the spin button is a single tab stop.
ARIA spin button HTML
<span id="tickets-label">
Tickets
</span>
<div class="spin-button">
<button
type="button"
tabindex="-1"
aria-label="Fewer tickets">−</button>
<div
role="spinbutton"
tabindex="0"
aria-labelledby="tickets-label"
aria-valuemin="1"
aria-valuemax="10"
aria-valuenow="2">2</div>
<button
type="button"
tabindex="-1"
aria-label="More tickets">+</button>
</div>Script handles the keys and the buttons. Every change goes through setValue(), which clamps the value to the range and updates aria-valuenow and the displayed number together, on the element that already has focus. A click on either button returns focus to the spin button, and both handlers ignore input while aria-disabled="true" is set.
This build doesn't accept typed entry. A custom spin button that does has to handle text editing as well, without blocking the browser's own editing keys.
ARIA spin button JavaScript
class CustomSpinButton {
constructor(element) {
this.element = element;
this.min = Number(element.getAttribute("aria-valuemin"));
this.max = Number(element.getAttribute("aria-valuemax"));
this.step = 1;
this.pageStep = 5;
const [decrease, increase] =
element.parentElement.querySelectorAll("button");
element.addEventListener("keydown", (event) =>
this.onKeydown(event)
);
decrease.addEventListener("click", () =>
this.stepBy(-this.step)
);
increase.addEventListener("click", () =>
this.stepBy(this.step)
);
}
get value() {
return Number(this.element.getAttribute("aria-valuenow"));
}
get disabled() {
return this.element.getAttribute("aria-disabled") === "true";
}
// Every change comes through here: clamp to the range, then
// update the reported value and the displayed number in place
setValue(value) {
const clamped = Math.min(this.max, Math.max(this.min, value));
this.element.setAttribute("aria-valuenow", String(clamped));
this.element.textContent = String(clamped);
}
stepBy(amount) {
if (this.disabled) return;
this.setValue(this.value + amount);
// Keep focus on the spin button after a button click
this.element.focus();
}
onKeydown(event) {
if (this.disabled) return;
const changes = {
ArrowUp: this.step,
ArrowDown: -this.step,
PageUp: this.pageStep,
PageDown: -this.pageStep,
};
if (event.key in changes) {
this.setValue(this.value + changes[event.key]);
} else if (event.key === "Home") {
this.setValue(this.min);
} else if (event.key === "End") {
this.setValue(this.max);
} else {
return;
}
// Keep the page from scrolling on the same key press
event.preventDefault();
}
}
document
.querySelectorAll('[role="spinbutton"]')
.forEach((element) => new CustomSpinButton(element));Structure and Semantics
Assistive technology conveys four things about a spin button: its spinbutton role, an accessible name that says what the number is for, its current value, and the limits of its range when it has them. All four belong on the one element users focus, the element that holds the value.
A native <input type="number"> conveys all of these through HTML. Its value, min, and max attributes set the value and the range, and an associated <label> names it. Don't add aria-valuenow, aria-valuemin, or aria-valuemax to a native spin button. aria-valuetext has no native counterpart, so it's allowed on either kind.
A custom spin button built from a generic element needs ARIA for each of these:
| Attribute | How to use |
|---|---|
role | Set to "spinbutton" on the element that holds the value and takes focus. An <input type="number"> has this role implicitly. |
aria-valuenow | Set to the current value, and update it every time the value changes. Leave it off only while the value is unknown. |
aria-valuemin | Set to the lowest allowed value when there is one. A spin button has no default minimum, so leaving it off means the range has none. |
aria-valuemax | Set to the highest allowed value when there is one. A spin button has no default maximum, so leaving it off means the range has none. |
aria-valuetext | Optional. Set to a readable form of the value, such as "Monday" for 2, when the number alone isn't meaningful. |
aria-invalid | Set to "true" while the value is outside the allowed range, and remove it once the value is back in range. |
aria-required | Set to "true" when the form can't be submitted without a value. A native spin button uses the required attribute instead. |
aria-disabled | Set to "true" to disable a custom spin button. A native spin button uses the disabled attribute instead. |
tabindex | Set to "0" on the spin button, and to "-1" on its increase and decrease buttons. |
The increase and decrease buttons are buttons in their own right, so each needs an accessible name, such as "More tickets", rather than a bare "+" or "−".
A custom spin button also isn't part of a form. To submit its value with the form, make it a form-associated custom element and set its value with ElementInternals.setFormValue(). For how, see the HTML Standard's form-associated custom element definition.
Role
A spin button exposes the spinbutton role on the element that holds its value and takes focus. An <input type="number"> carries the role implicitly and needs no role attribute. A custom spin button puts role="spinbutton" on the value element, not on a wrapper around it or on either of its buttons.
Has the correct role
Reported as: Element has incorrect role
A spin button must expose the spinbutton role so assistive technology can tell users it holds a number they can change.
When a custom spin button is built from a <div> without the role, or carries a different one, assistive technology presents it as plain text or as the wrong kind of control. Users aren't told that the arrow keys change it, and its value properties go unreported.
Use a native <input type="number">, or add role="spinbutton" to the value element of a custom spin button.
Severity: Critical
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 4.1.2 Name, Role, Value (A) | For all user interface components, the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to assistive technologies. |
| EN 301 549 | 9.4.1.2 Name, Role, Value | Same as the WCAG text. |
| Section 508 | 1194.21(d) | Sufficient information about a user interface element including the identity, operation and state of the element shall be available to assistive technology. When an image represents a program element, the information conveyed by the image must also be available in text. |
Applies role to the correct element
Reported as: Incorrect root element
Sanity check. Evinced checks that the spinbutton role sits on the element users focus and operate, not on an element around it or inside it. This check runs on every UI pattern, not only on spin buttons.
A spin button should carry the spinbutton role on the element users focus, not on a wrapper around it or an element inside it.
When the role sits on a wrapper while the tab stop and key handling sit on the value element inside it, assistive technology describes the focused element, which has no role or value. Users land on the spin button and aren't told what it is or what it's set to, even though role="spinbutton" is in the markup.
Put the role, the accessible name, the value properties, the tab stop, and the key handling on one element. Keep any wrapper free of roles and handlers.
Accessibility Label
A spin button's accessible name says what the number counts or sets, such as "Tickets" or "Minutes". It doesn't include the value, which assistive technology reports alongside the name. A native spin button takes its name from an associated <label>, and a custom one from aria-labelledby pointing to visible text, as in Examples, or from aria-label when there's no visible label.
Names a container that needs one
Reported as: Missing contextual labeling
A spin button must have an accessible name that says what its value is for.
When a spin button has no accessible name, assistive technology reports only its role and its number, so screen reader users hear that there's a spin button set to 2 with no sign of whether it counts tickets, guests, or nights. They can't tell what the value sets, so they can't choose it with any confidence. Visible text beside the field helps sighted users only, and voice control users have no name to speak.
Associate a <label> with a native spin button. For a custom spin button, point aria-labelledby at the visible label, and use aria-label only when there's no visible text to reference.
Severity: Critical
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 1.3.1 Info and Relationships (A) | Information, structure, and relationships conveyed through presentation can be programmatically determined or are available in text. |
| EN 301 549 | 9.1.3.1 Info and Relationships | Same as the WCAG text. |
aria-valuetext
When a spin button's number doesn't say what the value means, aria-valuetext gives assistive technology a readable form of it, such as a day name or a time with its unit. Assistive technology reports the text in place of the number, so the text changes every time the value does. It's allowed on a native <input type="number"> as well as on a custom spin button.
The following example gives a day-of-week picker the name of each day as its value text.
<span id="day-label">
Day
</span>
<div
role="spinbutton"
tabindex="0"
aria-labelledby="day-label"
aria-valuemin="1"
aria-valuemax="7"
aria-valuenow="2"
aria-valuetext="Monday">Monday</div>Uses value text to add meaning
Reported as: Meaningful aria-valuetext
A spin button's aria-valuetext should say something its number doesn't.
When aria-valuetext repeats the value, such as "2" for a value of 2, screen reader users hear nothing they wouldn't have heard anyway. The text also replaces the number in what assistive technology reports, so if it isn't updated with the value, screen reader users hear a value the spin button no longer holds.
Leave aria-valuetext off when the number speaks for itself. When the number needs explaining, set text that explains it, and update it in the same place as the value.
Severity: Minor
Values
A spin button reports its current value and, when it has them, the limits of its range, so users know what it's set to and how far it can go. Unlike the other range widgets, a spin button has no default limits: an undeclared minimum or maximum tells assistive technology the range has none, which is right only when that's true.
Leaves native values to HTML
Reported as: Avoid ARIA values on native inputs
A native <input type="number"> must not also carry aria-valuenow, aria-valuemin, or aria-valuemax.
When a native spin button has ARIA value properties alongside its own value, min, and max, the two can disagree. The browser updates value as users type or step the number, while the ARIA properties keep whatever code last wrote, so screen reader users can hear a number the field no longer holds.
Remove the ARIA value properties and let value, min, and max report the state. aria-valuetext has no native counterpart, so keep it when the number needs explaining.
Severity: Critical
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 4.1.2 Name, Role, Value (A) | For all user interface components, the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to assistive technologies. |
| EN 301 549 | 9.4.1.2 Name, Role, Value | Same as the WCAG text. |
| Section 508 | 1194.21(d) | Sufficient information about a user interface element including the identity, operation and state of the element shall be available to assistive technology. When an image represents a program element, the information conveyed by the image must also be available in text. |
Orders its limits low to high
Reported as: Min value is greater than the max value
A spin button's minimum must be lower than its maximum.
When the minimum is greater than the maximum, the range contains no value. Screen reader users hear a number with no sense of where it falls, Home and End no longer have a clear meaning, and every value users enter falls outside the range.
Sort the two limits before declaring them, including limits taken from data, so the smaller one is always the minimum.
Severity: Critical
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 4.1.2 Name, Role, Value (A) | For all user interface components, the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to assistive technologies. |
| EN 301 549 | 9.4.1.2 Name, Role, Value | Same as the WCAG text. |
| Section 508 | 1194.21(d) | Sufficient information about a user interface element including the identity, operation and state of the element shall be available to assistive technology. When an image represents a program element, the information conveyed by the image must also be available in text. |
Reports its value and its limits
Reported as: Missing range attributes
A spin button must report its current value, and the limits of its range when it has them, to assistive technology.
When a custom spin button changes its displayed number but never sets aria-valuenow, screen reader users learn they're on a spin button but not what it reads. Missing limits fail more quietly. A spin button has no default range, so users aren't told that the value stops at 10 and can't tell the end of the range from a control that has stopped responding.
Set aria-valuenow on a custom spin button and update it in the same code that changes the displayed number. Set aria-valuemin and aria-valuemax whenever the range has limits. A native <input type="number"> reports all three from value, min, and max.
Severity: Critical
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 4.1.2 Name, Role, Value (A) | For all user interface components, the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to assistive technologies. |
| EN 301 549 | 9.4.1.2 Name, Role, Value | Same as the WCAG text. |
| Section 508 | 1194.21(d) | Sufficient information about a user interface element including the identity, operation and state of the element shall be available to assistive technology. When an image represents a program element, the information conveyed by the image must also be available in text. |
Spans more than a single value
Reported as: Range has equal min and max values
A spin button's range must have room for more than one value, with a maximum above its minimum.
When the minimum and maximum are equal, such as a quantity limit taken from stock that has one item left, the spin button can hold only one value. Users press keys or buttons with no effect, and they can't tell a spin button fixed at one value from one that has stopped responding.
Check that the limits differ before rendering the spin button. When they don't, show the single value as text, or disable the spin button and say why.
Severity: Critical
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 4.1.2 Name, Role, Value (A) | For all user interface components, the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to assistive technologies. |
| EN 301 549 | 9.4.1.2 Name, Role, Value | Same as the WCAG text. |
| Section 508 | 1194.21(d) | Sufficient information about a user interface element including the identity, operation and state of the element shall be available to assistive technology. When an image represents a program element, the information conveyed by the image must also be available in text. |
aria-invalid
A spin button that accepts typed entry can hold a value outside its range: a text field takes whatever users type, and a native <input type="number"> doesn't correct it. aria-invalid="true" reports that error while it lasts and comes off as soon as the value is back in range. Point aria-describedby or aria-errormessage at a message that says which values are allowed.
Flags invalid only when out of range
Reported as: False aria-invalid value
A spin button must not report a value inside its range as invalid.
When aria-invalid="true" stays on a spin button after users correct the value, or is written into the markup as a hook for error styling, screen reader users are told a correct number is wrong. They may keep changing a value that was fine, or abandon a form they completed correctly.
Set aria-invalid from the current value each time it changes: true only while the value is outside the range, and removed otherwise. Drive error styling from a class, not from the attribute.
Severity: Serious
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 4.1.2 Name, Role, Value (A) | For all user interface components, the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to assistive technologies. |
| EN 301 549 | 9.4.1.2 Name, Role, Value | Same as the WCAG text. |
| Section 508 | 1194.21(d) | Sufficient information about a user interface element including the identity, operation and state of the element shall be available to assistive technology. When an image represents a program element, the information conveyed by the image must also be available in text. |
Keeps the value within its bounds
Reported as: Value out of range
A spin button's reported value must sit at or between its minimum and maximum.
When the value comes from data with a different range, or the limits change without the value following, the spin button reports a number its range can't contain. Screen reader users may hear 12 on a spin button that stops at 10, and stepping from a value outside the range behaves unpredictably.
Clamp the value to the limits whenever the value or the limits change, and set all three from the same source.
Severity: Serious
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 4.1.2 Name, Role, Value (A) | For all user interface components, the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to assistive technologies. |
| EN 301 549 | 9.4.1.2 Name, Role, Value | Same as the WCAG text. |
| Section 508 | 1194.21(d) | Sufficient information about a user interface element including the identity, operation and state of the element shall be available to assistive technology. When an image represents a program element, the information conveyed by the image must also be available in text. |
Disabled
A disabled spin button reports its disabled state in one way, suited to how it's built. A native <input type="number"> uses the disabled attribute, which also removes it from the tab order and blocks input. A custom spin button uses aria-disabled="true", which only reports the state, so code has to ignore key presses and button clicks, and manage the tab stop.
Avoids conflicting disabled states
Reported as: Conflicting disabled values
A spin button must have a single, consistent disabled state.
When a spin button sets its disabled state two ways and the values disagree, such as the disabled attribute alongside aria-disabled="false", assistive technology may report a state the browser doesn't enforce. Users can't tell whether they're able to change the number.
Set the disabled state once: the disabled attribute on a native spin button, or aria-disabled on a custom one, never both.
Severity: Serious
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 4.1.2 Name, Role, Value (A) | For all user interface components, the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to assistive technologies. |
| EN 301 549 | 9.4.1.2 Name, Role, Value | Same as the WCAG text. |
| Section 508 | 1194.21(d) | Sufficient information about a user interface element including the identity, operation and state of the element shall be available to assistive technology. When an image represents a program element, the information conveyed by the image must also be available in text. |
Uses aria-disabled rather than disabled
Reported as: Element does not support the disabled attribute
A custom spin button must convey its disabled state with aria-disabled, not the disabled attribute.
When disabled is set on the <div> or <span> behind a custom spin button, the browser ignores it. The spin button stays in the accessibility tree as an ordinary, available control, so users are told they can change a number the design means to be locked, even when styling grays it out.
Set aria-disabled="true" instead, and ignore the keys and the button clicks in code while it's set. When a native <input type="number"> would do the job, switch to it, and its disabled attribute handles the state, the tab order, and input.
Severity: Serious
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 4.1.2 Name, Role, Value (A) | For all user interface components, the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to assistive technologies. |
| EN 301 549 | 9.4.1.2 Name, Role, Value | Same as the WCAG text. |
| Section 508 | 1194.21(d) | Sufficient information about a user interface element including the identity, operation and state of the element shall be available to assistive technology. When an image represents a program element, the information conveyed by the image must also be available in text. |
Disables only elements that support it
Reported as: Element type does not support disabled state
An element in a spin button must carry disabled or aria-disabled only when its role supports a disabled state.
When the state sits on an element whose role has none, such as a wrapper, a list item or a heading, assistive technology may drop it or report it on something users can't operate, while the control that really is unavailable is still reported as available.
Remove the attribute from that element, and set the disabled state on the control itself, using an element or role that supports it, such as a native <button>.
Severity: Serious
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 4.1.2 Name, Role, Value (A) | For all user interface components, the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to assistive technologies. |
| EN 301 549 | 9.4.1.2 Name, Role, Value | Same as the WCAG text. |
| Section 508 | 1194.21(d) | Sufficient information about a user interface element including the identity, operation and state of the element shall be available to assistive technology. When an image represents a program element, the information conveyed by the image must also be available in text. |
Forbidden Interactive Children
ARIA limits what a spin button may contain to its own parts: a text field and two buttons. Any other interactive element inside it, such as a link or a menu button, is forbidden. Putting the role on the value element and keeping the buttons beside it as siblings is the other construction ARIA allows, and it leaves nothing interactive inside the spin button.
Avoids nested interactives
Reported as: Forbidden interactive children; Nested interactive elements
A spin button must not contain an interactive element other than its text field and its increase and decrease buttons.
When a link, a menu button, or another control sits inside a spin button, the roles overlap. Assistive technology may drop the inner element's role and name, or report the spin button in its place, so screen reader users hear one control where two exist, and a key press or a click has two plausible targets.
Move the extra control out of the spin button, and out of any aria-owns list on it, and place it beside the spin button with its own role, accessible name, and tab stop.
Severity: Serious
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 4.1.2 Name, Role, Value (A) | For all user interface components, the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to assistive technologies. |
| EN 301 549 | 9.4.1.2 Name, Role, Value | Same as the WCAG text. |
| Section 508 | 1194.21(d) | Sufficient information about a user interface element including the identity, operation and state of the element shall be available to assistive technology. When an image represents a program element, the information conveyed by the image must also be available in text. |
Relationships
A spin button relates to its label, to any text that explains it, and to its increase and decrease buttons. aria-labelledby points to the label, and aria-describedby points to supporting text, such as the allowed range or an error message. The buttons usually sit beside the spin button in the DOM, as siblings, and ARIA also allows them inside it as children.
aria-owns
A spin button's parts are either its children in the DOM or its siblings, and either construction is complete without aria-owns.
Avoids aria-owns
Reported as: Avoid aria-owns
General check. Evinced checks that the spin button doesn't use aria-owns to claim its buttons or other elements as its children. This check runs on every UI pattern, not only on spin buttons.
A spin button should not own its increase and decrease buttons, or any other element, with aria-owns.
When a spin button owns buttons that sit elsewhere in the DOM, support varies by browser and assistive technology, so some screen reader users get the buttons as part of the spin button and others don't. Owned elements also land after the spin button's own children, so the order users hear can differ from the order on screen.
Remove aria-owns, and keep the buttons beside the spin button as siblings or nest them inside it in the DOM.
Operation
With a pointer, users activate the increase and decrease buttons to step the value, or click the field to type into it. With a keyboard, the Tab key moves focus to the spin button and the keys in the following table change its value. Each change updates the value the spin button reports: value on a native spin button, and aria-valuenow, with aria-valuetext when it's set, on a custom one.
| Key | Function |
|---|---|
| Up Arrow | Increases the value by one step |
| Down Arrow | Decreases the value by one step |
| Home | Sets the value to the minimum, when there is one |
| End | Sets the value to the maximum, when there is one |
| Page Up (optional) | Increases the value by a larger step than Up Arrow |
| Page Down (optional) | Decreases the value by a larger step than Down Arrow |
A spin button that accepts typed entry also supports the standard text-editing keys, such as those for selecting, copying, and pasting. Script mustn't capture the keys the browser uses for editing. Focus stays on the spin button throughout, including after users activate one of its buttons.
A native <input type="number"> handles the arrow keys and typed entry on its own. A custom spin button has to reproduce the keys in code, with a keydown listener that changes the value through one function, as the Custom ARIA Spin Button example shows.
Focus Trap
Focus stays on a spin button while users change its value, from the keyboard or with its buttons. The value changes in place, on the element that already has focus, so every key press reaches the same spin button.
Keeps focus after a value change
Reported as: Focus lost after spinbutton interaction
A spin button must keep focus when users change its value with the keyboard.
When each key press re-renders the spin button, the focused element is replaced and focus drops to the page. The next press goes nowhere or scrolls the page, and screen reader users aren't told that focus has moved, so they find out only when the value stops responding.
Update the displayed number and aria-valuenow on the existing element rather than rebuilding it. When a re-render can't be avoided, return focus to the spin button afterward.
Severity: Serious
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 3.2.2 On Input (A) | Changing the setting of any user interface component does not automatically cause a change of context unless the user has been advised of the behavior before using the component. |
| EN 301 549 | 9.3.2.2 On Input | Same as the WCAG text. |
| Section 508 | 1194.22(n) | When electronic forms are designed to be completed on-line, the form shall allow people using assistive technology to access the information, field elements, and functionality required for completion and submission of the form, including all directions and cues. |
| Section 508 | 1194.21(l) | When electronic forms are used, the form shall allow people using assistive technology to access the information, field elements, and functionality required for completion and submission of the form, including all directions and cues. |
Focus Sequence
An enabled spin button is a single tab stop, on the element that carries the spinbutton role. Its increase and decrease buttons stay out of the tab order, and the Up Arrow and Down Arrow keys do their work instead. A native <input type="number"> is focusable on its own, and a custom spin button needs tabindex="0" on the value element and tabindex="-1" on each button.
Reachable by keyboard
Reported as: Keyboard accessible
A spin button must be reachable with the Tab key.
When a custom spin button is built from a <div> whose buttons respond to clicks but which has no tabindex, keyboard users can't reach it, so they can't change its value, however correct its role and value properties are.
Use a native <input type="number">, which is focusable on its own, or add tabindex="0" to the element that carries the spinbutton role.
Severity: Critical
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 2.1.1 Keyboard (A) | All functionality of the content is operable through a keyboard interface without requiring specific timings for individual keystrokes, except where the underlying function requires input that depends on the path of the user's movement and not just the endpoints. |
| EN 301 549 | 9.2.1.1 Keyboard | Same as the WCAG text. |
| Section 508 | 1194.21(a) | When software is designed to run on a system that has a keyboard, product functions shall be executable from a keyboard where the function itself or the result of performing a function can be discerned textually. |
| 508 Trusted Tester | 2.1.1-keyboard-access, 4.A | All functionality can be accessed and executed using only the keyboard. |
Skips tab order when disabled
Reported as: Disabled element focusable
A spin button that presents as disabled should not be in the tab order.
When a disabled spin button keeps its tab stop, keyboard users land on a number they can't change. In a form with several disabled controls, those stops add up.
Use the disabled attribute on a native spin button, which removes it from the tab order. On a custom spin button, set tabindex="-1" while aria-disabled="true" is set, and restore tabindex="0" when it becomes available.
Keeping a disabled custom spin button focusable is a recognized alternative, since users can then find it, learn its value, and learn that it's unavailable. If you take that route, make sure the disabled state is exposed and key input is ignored.
Severity: Minor
Element that carries role receives focus
Reported as: Focusable offset
A spin button must receive focus on the element that carries the spinbutton role, not on one of its descendants.
When the role sits on a wrapper and focus lands on a text field or a button inside it, assistive technology describes the focused element, which carries none of the spin button's role or value. Users tabbing through the page aren't told they've reached a spin button or what it's set to.
Put the role and the tab stop on the same element. When the spin button has a text field, put the role on the text field and keep the buttons beside it, rather than wrapping all three in an element that carries the role.
Severity: Minor
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 2.1.1 Keyboard (A) | All functionality of the content is operable through a keyboard interface without requiring specific timings for individual keystrokes, except where the underlying function requires input that depends on the path of the user's movement and not just the endpoints. |
| EN 301 549 | 9.2.1.1 Keyboard | Same as the WCAG text. |
| Section 508 | 1194.21(a) | When software is designed to run on a system that has a keyboard, product functions shall be executable from a keyboard where the function itself or the result of performing a function can be discerned textually. |
| 508 Trusted Tester | 2.1.1-keyboard-access, 4.A | All functionality can be accessed and executed using only the keyboard. |
Context Change
Changing a spin button's value changes only that value, even though each arrow key press or typed digit is a change of its own. Anything more, such as submitting the form, loading another page, or moving focus elsewhere, waits for an explicit control. Focus lost because the spin button re-renders is covered under Focus Trap.
Keeps context when a value changes
Reported as: Unexpected context change on input
Changing a spin button's value must not change the context unless users are told beforehand that it will.
When a new value submits the form, loads another page, or moves focus, as a quantity field that reloads the cart on every change does, users stepping toward a value set off the change at the first press and never reach it. Screen reader and screen magnifier users may not notice that the page changed.
Update the page in place and leave focus on the spin button. Start submission or navigation from an explicit control such as an Update button, or, when the change of context is intended, say so in text that comes before the spin button.
Severity: Serious
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 3.2.2 On Input (A) | Changing the setting of any user interface component does not automatically cause a change of context unless the user has been advised of the behavior before using the component. |
| EN 301 549 | 9.3.2.2 On Input | Same as the WCAG text. |
| Section 508 | 1194.22(n) | When electronic forms are designed to be completed on-line, the form shall allow people using assistive technology to access the information, field elements, and functionality required for completion and submission of the form, including all directions and cues. |
| Section 508 | 1194.21(l) | When electronic forms are used, the form shall allow people using assistive technology to access the information, field elements, and functionality required for completion and submission of the form, including all directions and cues. |
Keyboard Navigation
A spin button changes its value from the keyboard in steps, and every change stays inside the range and updates the reported value along with the displayed number. A custom spin button handles the keys in a keydown listener and calls preventDefault() for each key it handles, so the page doesn't also scroll.
The deprecated keypress event doesn't fire for the arrow keys, Home, End, or the page keys, so it can't operate a spin button.
Changes value with the keyboard
Reported as: Spinbutton value unchanged with keyboard
A spin button must change its value, and update aria-valuenow, when users press the Up Arrow or Down Arrow key.
When the displayed number moves but aria-valuenow keeps the value it was rendered with, screen reader users are told the value hasn't changed. They press again, overshoot, and submit a number other than the one they were told they'd chosen. A spin button that ignores the keys altogether leaves keyboard users nothing, since its buttons aren't in the tab order.
Handle Up Arrow and Down Arrow in a keydown listener, and set aria-valuenow from the same state that renders the number. A native <input type="number"> handles both keys on its own.
Severity: Critical
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 2.1.1 Keyboard (A) | All functionality of the content is operable through a keyboard interface without requiring specific timings for individual keystrokes, except where the underlying function requires input that depends on the path of the user's movement and not just the endpoints. |
| EN 301 549 | 9.2.1.1 Keyboard | Same as the WCAG text. |
| Section 508 | 1194.21(a) | When software is designed to run on a system that has a keyboard, product functions shall be executable from a keyboard where the function itself or the result of performing a function can be discerned textually. |
| 508 Trusted Tester | 2.1.1-keyboard-access, 4.A | All functionality can be accessed and executed using only the keyboard. |
Keeps the value in range
Reported as: Unexpected spinbutton value
A spin button's value must stay between its minimum and maximum, and stay a number, as users operate it with the keyboard.
When a key handler adds or subtracts a step without checking the limits, pressing Up Arrow at the maximum pushes aria-valuenow past it. When the arithmetic runs on text rather than on a parsed number, the value can end up empty or unreadable, so screen reader users are told nothing about a field they've just changed.
Clamp every change to the limits in one function that all the keys share, and parse the current value to a number before stepping it.
Severity: Serious
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 4.1.2 Name, Role, Value (A) | For all user interface components, the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to assistive technologies. |
| EN 301 549 | 9.4.1.2 Name, Role, Value | Same as the WCAG text. |
| Section 508 | 1194.21(d) | Sufficient information about a user interface element including the identity, operation and state of the element shall be available to assistive technology. When an image represents a program element, the information conveyed by the image must also be available in text. |
Ignores keyboard when disabled
Reported as: Disabled spinbutton operable
A spin button that reports itself as disabled must not change its value from the keyboard.
When a custom spin button carries aria-disabled="true" but its key handler still runs, screen reader users are told the value is locked and then find that it changes. The new value may be submitted without anything flagging it.
Check the disabled state at the top of the key handler and the button handlers, and return before changing anything. On a native spin button, the disabled attribute blocks input for you.
Severity: Moderate
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 1.3.1 Info and Relationships (A) | Information, structure, and relationships conveyed through presentation can be programmatically determined or are available in text. |
| EN 301 549 | 9.1.3.1 Info and Relationships | Same as the WCAG text. |
Sets the maximum with End
Reported as: Spinbutton does not support End key
A spin button with a maximum must set its value to that maximum when users press the End key.
When End isn't handled, reaching the top of the range takes one Up Arrow press per step, and screen reader users hear every value on the way. Users often settle for a value short of the one they wanted.
Handle End in the same keydown listener as the arrow keys, set the value to the maximum, and call preventDefault() so the key does nothing else. When the range has no maximum, leave End unhandled.
Severity: Moderate
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 2.1.1 Keyboard (A) | All functionality of the content is operable through a keyboard interface without requiring specific timings for individual keystrokes, except where the underlying function requires input that depends on the path of the user's movement and not just the endpoints. |
| EN 301 549 | 9.2.1.1 Keyboard | Same as the WCAG text. |
| Section 508 | 1194.21(a) | When software is designed to run on a system that has a keyboard, product functions shall be executable from a keyboard where the function itself or the result of performing a function can be discerned textually. |
| 508 Trusted Tester | 2.1.1-keyboard-access, 4.A | All functionality can be accessed and executed using only the keyboard. |
Sets the minimum with Home
Reported as: Spinbutton does not support Home key
A spin button with a minimum must set its value to that minimum when users press the Home key.
When Home isn't handled, the value stays where it was, and reaching the bottom of the range takes one Down Arrow press per step. On a range of a hundred values, that's impractical for anyone who can't use the buttons with a pointer.
Handle Home alongside End and the arrow keys, set the value to the minimum, and call preventDefault() so the key does nothing else. When the range has no minimum, leave Home unhandled.
Severity: Moderate
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 2.1.1 Keyboard (A) | All functionality of the content is operable through a keyboard interface without requiring specific timings for individual keystrokes, except where the underlying function requires input that depends on the path of the user's movement and not just the endpoints. |
| EN 301 549 | 9.2.1.1 Keyboard | Same as the WCAG text. |
| Section 508 | 1194.21(a) | When software is designed to run on a system that has a keyboard, product functions shall be executable from a keyboard where the function itself or the result of performing a function can be discerned textually. |
| 508 Trusted Tester | 2.1.1-keyboard-access, 4.A | All functionality can be accessed and executed using only the keyboard. |
Event Listener Type
When script in a spin button responds to keys, it listens for keydown or keyup. The keypress event is deprecated, and browsers don't fire it for many keys, including the arrow keys, Home, End and Escape.
Avoids the keypress event
Reported as: Deprecated keypress listener
A spin button's key handling should use keydown or keyup, not keypress.
When a handler listens for keypress, keys the browser doesn't send through that event go unanswered, and keyboard users find the spin button ignoring keys it's meant to respond to.
Replace each keypress listener with a keydown listener, and read the key from event.key.
Severity: Minor
FAQ
Which WCAG success criteria do the spin button issues relate to?
Most relate to 4.1.2 Name, Role, Value: the spinbutton role, reporting the value and its limits, ordering the limits and keeping the value within them, the invalid state, how the disabled state is declared, nested interactive elements, and leaving native values to HTML. The accessible name relates to 1.3.1 Info and Relationships, as does ignoring the keyboard while disabled.
Reaching the spin button, taking focus on the element with the role, and changing the value with the arrow keys, Home, and End relate to 2.1.1 Keyboard. Keeping focus and context when the value changes relates to 3.2.2 On Input. Meaningful value text and keeping a disabled spin button out of the tab order map to no WCAG success criterion and are best practices.
Why are some spin button issues marked as Critical and others as Minor?
Severity reflects how much an issue gets in users' way. Critical issues stop users outright:
- A spin button with no role, value, or limits
- A spin button with no accessible name
- Limits in the wrong order or equal
- ARIA values that contradict a native spin button
- A spin button keyboard users can't reach or change
Serious issues confuse users though tasks are still possible:
- A wrong invalid or disabled state
- A value outside the range
- A link or other control nested inside the spin button
- Focus lost, or a page loaded or form submitted, when the value changes
Moderate issues make the keyboard harder to use:
- Missing Home and End keys
- A disabled spin button that still changes from the keyboard
Minor issues are smaller obstacles:
- Value text that adds nothing
- A disabled spin button in the tab order
- Focus on an element other than the one with the role
The aria-owns check and the check on where the role sits carry no severity: Evinced reports them as general and sanity checks.
Related Patterns
- Slider: a value in a range set by moving a thumb along a track
- Meter: a value within a known range that users can't change
- Progress Bar: the progress of a task, which users can't change
- Text Input: free entry of digits that aren't a quantity, such as a postal code
- Radio Group: a single choice from a short list of named options
- Listbox: a choice from a list of named options
- Button: the role of a spin button's increase and decrease buttons