Reordering Lists with aria-owns
Using aria-owns to make list re-ordering accessible.
Background
Imagine you want to create a yet another TODO application. You want to allow your users to re-order their tasks in addition to editing the text.
Re-ordering items using CSS (e.g. using the flex
order
property) can cause problems for accessibility. The
general advice is to ensure that the DOM order of content matches the visual
order. This can lead to other problems, which will be addressed later.
By using aria-owns
alongside CSS-based reordering, we can
ensure screenreaders are able to navigate the content in the appropriate
order.
The Problems
- Screenreaders, such as NVDA or VoiceOver, generally navigate content in the order it appears in the DOM
- Removing an item from the DOM (including reattaching it elsewhere) removes any undo/redo edit history the browser is maintaining
Re-ordering using CSS
If you reorder only using CSS, a screenreader may not navigate the content
in the correct order. In the following video, an item is re-ordered within a
flex parent container using the CSS order
property.
In the above video, I am using Windows Narrator, pressing caps lock + right arrow, to navigate sequentially through the content. Note that after reordering the items, the content is still read in the original order. The item with the text "world" is read as the second list item, rather than the third.
Re-ordering in the DOM
To solve the CSS issue, it can be tempting to instead reorder items by
reordering them in the DOM. For example, in React you may re-order an array
of items representing your list and use a
key
for each item when rendering the associated DOM.
This causes two problems:
- Focus is not properly maintained
- Any text the user may have entered can no longer be undone
Some DOM rendering libraries attempt to address the former issue by refocusing any element that had focus prior to a re-ordering. However, this may cause a screenreader to read the item again, which can be confusing to the user.
The latter issue is not possible to solve without keeping items untouched
within the DOM. The browser maintains a stack of text edits to undo/redo
across all <input>
, <textarea>
and
contenteditable
elements. The stack is used when undo/redo are
invoked via a keyboard shortcut, application menu,
document.execCommand
or gesture (such as shake to undo on iOS).
Removing an element from the DOM removes any corresponding entries in the stack and adding it back does not restore it. Since inserting an element at a new location first removes it from the DOM, we cannot move the DOM elements if we wish to preserve the stack.
Using aria-owns
The
aria-owns
attribute allows us to expose a different ordering from the DOM to assistive
technologies.
Imagine in the above video, I had used aria-owns
to restructure
the accessibility tree, with structure looking something like:
<ul aria-owns="item-1 item-3 item-2">
<li id="item-1" style="order: 1">Hello</li>
<li id="item-2" style="order: 3">World</li>
<li id="item-3" style="order: 2">Test</li>
</ul>
With this change, the item with text "Test" will now be the second list item. The browser exposes the desired order to the screenreader and the user is able to properly navigate the content. Since we did not move any DOM elements, any text the user entered can be undone. The below video shows the updated result.
See an example of this in action see the following demo: https://sparhami.github.io/aria-owns-list-reorder/. In the example, you may expect that performing an undo would also undo the reordering, but that is a topic for a future post.
Demo
The following is a live demo of using aria-owns
and a flex
parent using order
to reorder items.
Links: