How do you support incrementing a `role="spinbutton"` with Voiceover?
Tags: help, a11y, html, javascript
Published: 2019-12-05 13:07:55

I'm struggling to implement an accessible input with increment/decrement behaviors, using an `<input type="text" role="spinbutton" />` element in HTML/JavaScript. But it seems like with VoiceOver there are custom (fake?) input events for incrementing/decrementing which change the input's value in unexpected ways.

How do I make sure a user navigating the page with assistive technology doesn't get bad guidance from VoiceOver while trying to interact with my widget?

For example, using super trimmed-down code like this:

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

    function logIt(...args) {
      document.getElementById('output').appendChild(
        document.createTextNode(`${args.join(', ')}\n`)
      );
    }

    document.getElementById("myInput").addEventListener('input', (e) => {
      debugger;
      logIt(e.type, e.data, String(e));
      e.target.setAttribute('aria-valuenow', e.target.value);
    }, false);

    document.getElementById("myInput").addEventListener('keydown', (e) => {
      logIt(e.type, e.data, String(e));
    }, false);

<!-- language: lang-html -->

          <input
            id="myInput"
            type="text"
            role="spinbutton"
            autocomplete="off"
            defaultValue="1"
            aria-valuenow="1"
            aria-valuemin="0"
            aria-valuemax="100"
          />
        </div>
        <pre id="output"></pre>

<!-- end snippet -->

... VoiceOver will describe the input as a "stepper" and give instructions on how to increment/decrement using the keyboard. But using those keyboard commands results in some weird math, as seen in this screen cap:

[![animated screen capture of attempts to increment with VoiceOver keyboard][1]][1]

You can also see (from the "logging" in the screen cap) that when the user types input, an `InputEvent` is triggered with `event.type` being `input` - but when the VoiceOver keyboard command for increment/decrement is used, a base-type `Event` is triggered with `event.type` again set to `input`.

And this doesn't seem to be unique to my implementations of `role="spinbutton"`. The jQuery UI spinner doesn't behave well when incremented/decremented using VoiceOver keyboard commands:

[![animated screen capture of jQuery UI spinner being incremented using VoiceOver and the value changing to some unexpected scientific notation][2]][2]

I even tried some of the w3c's examples for `role="spinbutton"`:

 - The font size widget on https://www.w3.org/TR/wai-aria-practices/examples/toolbar/toolbar.html
 - The date picker on https://www.w3.org/TR/wai-aria-practices/examples/spinbutton/datepicker-spinbuttons.html

... and even though VoiceOver described each of those UI controls as a "stepper" and gave instructions on how to increment/decrement them using the keyboard, those instructions didn't seem to work. Other keyboard behaviors worked - but the ones VoiceOver suggests don't.

What can I do to make sure that `role="spinbutton"` markup works correctly with VoiceOver's increment/decrement keyboard commands?


  [1]: https://i.stack.imgur.com/RQuPE.gif
  [2]: https://i.stack.imgur.com/pr4yM.gif