Enter The Dragon (Drop): Accessible List Reordering

About The Author

Harris is a web developer with a strong passion for digital equality. He works at Deque Systems as the Principal UI Engineer building awesome web applications. … More about Harris ↬

Email Newsletter

Weekly tips on front-end & UX.
Trusted by 200,000+ folks.

One of the toughest challenges Harris Schneiderman came across is the reorderable drag-and-drop list. While a reorderable list is a somewhat commonly used widget with intuitive conventions for mouse users, it’s not clear how keyboard-only assistive technology users can perform this simple task. Dragon Drop seeks to fill this gap by providing means for all users to perform this somewhat common task. Due to the absence of supported ARIA attributes, Dragon Drop utilizes live regions to convey the information needed for all users to reorder a list.

There are tons of drag-and-drop list reordering modules out there today, very few of them are built with accessibility in mind. Dragon Drop seeks to fill this gap by providing means for all users to perform this somewhat common task.

Over the years of being a web developer with a focus on accessibility, I have mostly dealt with widely-adopted, standardized UI components, well supported by assistive technologies (AT). For these types of widgets, there are concise ARIA authoring practices as well as great tools like axe-core that can be used to test web components for accessibility issues. Creating less common widgets, especially those that have no widely-adopted conventions for user interaction can be very tricky.

One of the toughest challenges I’ve come across is the reorderable drag-and-drop list. While a reorderable list is a somewhat commonly used widget with intuitive conventions for mouse users, it’s not clear how keyboard-only assistive technology users can perform this simple task. Due to the absence of supported ARIA attributes, Dragon Drop utilizes live regions to convey the information needed for all users to reorder a list.

Live Regions

Live regions are HTML elements equipped with ARIA attributes that can be used to notify screen readers of content changes. They allow us to provide a completely customized screen reader announcement without having to move focus! Live regions are great for real-time updates in remote parts of the page, status updates, and time-sensitive information.

They’re commonly used in chat logs, progress indicators, sports score updates, and weather alerts but should be used sparingly as they can easily create an overly-verbose experience for screen reader users. If you are new to live regions or just want to explore what they can do, check out my live region playground, which allows you to configure your own custom live region.

If you want to use live regions in your application, refer to the Live Region module on npm.

<div aria-live="assertive" role="log" aria-relevant="additions" aria-atomic="true"></div>

Why Use Live Regions?

In an ideal world, I would’ve been able to simply rely on the drag-and-drop ARIA attributes aria-grabbed and aria-dropeffect. However, in reality, the support for these attributes is rare, and where supported, the experience is confusing and counterintuitive for screen reader users. On top of that, these two attributes have been deprecated since ARIA 1.1, which means we won’t see support for these attributes grow in the future.

The W3C conversation about this deprecation can be found here. Because of these problems, I decided against using aria-grabbed and aria-dropeffect in Dragon Drop. Varying support for ARIA attributes within the vast array of assistive technology/browser pairings is quite prevalent in the world of accessibility. Luckily, live region attributes such as role, aria-live, aria-relevant, and aria-atomic are quite widely supported by screen readers such as JAWS, NVDA, and VoiceOver.

Optimized Accessibility

Dragon Drop is a highly configurable list reordering module that works for mouse, keyboard and assistive technology users. A couple of years ago, when I decided to write it, accessibly reordering lists had been brought up several times on projects I’d been working on but I had yet to see a working solution. Of the dozens of drag-and-drop list reorder plugins I came across, most of them weren’t designed with accessibility in mind and, as a result, were very inaccessible.

Dragon Drop has been tested with VoiceOver, JAWS and NVDA and enables AT users to successfully reorder a list.

I set out to answer all of the questions that any user might have when encountering a reorderable list. These questions have been answered for sighted mouse users through common conventions, but what about the rest of the users?

How Can I Pick Up An Item?

By providing a control which conveys the grabbed state of an item, along with some top-level help text, we can answer this question. For example, a control with the accessible text (gathered by AT though it’s name, role and value) “Reorder Item 1, toggle button” tells the user that it is a button that when activated, will pick the item up and kick off a reordering.

Dragon drop uses the aria-pressed attribute to let AT users know when an item is being “dragged” and when it is not. It can be configured to allow an entire item to be draggable, or just a child “drag handle”, which in either case the presence of the role="button" and tabindex="0" is ensured.

When a drag item is activated, aria-pressed="true" is applied to the element and a configurable announcement, such as “Item 1 grabbed” is read out to screen readers via the live region.

How Can I Move An Item?

Utilizing aria-describedby to associate the controls with useful help text such as “Activate the reorder button and use the arrow keys to reorder the list or use your mouse to drag/reorder.” tells the user how to go about actually reordering. This lets screen reader users know that when an item is pressed, the up and down arrow keys will move the item up and down the list respectively.

How Can I Drop An Item?

Because the aria-pressed attribute is used, users can easily tell how to drop an item. In some way, shape or form, all of the most widely used screen readers convey the pressed state of a toggle button. The aria-pressed attribute is set to “false” when an item is dropped and a customized announcement such as “Item 1 dropped” is read out to screen readers.

How Do I Know When The List Has Been Reordered?

Every time the up and down arrow keys are used to alter the order of the list, we need to ensure that all users are notified of this change. For non-sighted users, we must once again rely on live regions. A configurable announcement such as “The list has been reordered, Item 1 is now 4th in the list.”, is read out to convey the updated state of the reordered list. This is important because sighted users have immediate visual feedback of the altered order and that very same information needs to be conveyed to AT users.

How Do I Cancel The Reordering?

Since we can’t rely on a widely adopted convention for such interaction, we can simply include directions such as “Press escape to cancel the reordering” within the help text. In addition, we utilize the live region to provide a customized readout that notifies the user of the cancellation.

Keyboard Interaction

KeyBehavior
Enter or SpaceToggles the item between the "grabbed" and "dropped" states
"↓"Moves a "grabbed" item down in the list
"↑"Moves a "grabbed" item up in the list
EscCancels reordering and restores initial order

Seeing The Dragon In Action

Check the Dragon Drop demo in which you experience a couple different configurations.

Dropping Dragon Drop Into Your App

Dragon Drop converts your ordinary list into a fully accessible drag-and-drop reorderable list:

<ul id="dragon">
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
</ul>
<script>
  const dragon = document.getElementById('dragon');
  // Enter the dragon
  new DragonDrop(dragon);
</script>

Installation

Dragon Drop is an open source (MIT License) project and can be installed via npm:

$ npm install drag-on-drop

It can be used with modules like browserify or webpack:

// if you're not down with ES6, you can require('drag-on-drop')
import DragonDrop from 'drag-on-drop';

Dragon Drop can also be easily dropped into your page with the unpkg CDN:

<script source="https://unpkg.com/drag-on-drop"></script>
<script>
  var dragonDrop = new DragonDrop(listElement);
</script>

Configuration

To support a wide range of use cases, Dragon Drop is very configurable.

Below is the default configuration:

{
  item: 'li',
  handle: 'button',
  activeClass: 'dragon-active',
  inactiveClass: 'dragon-inactive',
  announcement: {
    grabbed: el => `Item ${el.innerText} grabbed`,
    dropped: el => `Item ${el.innerText} dropped`,
    reorder: (el, items) => {
      const pos = items.indexOf(el) + 1;
      const text = el.innerText;
      return `The list has been reordered, ${text} is now item ${pos} of ${items.length}`;
    },
    cancel: 'Reordering cancelled'
  }
}

Announcements

Dragon Drop’s “announcement” configuration option is the most important because is the backbone of the screen reader experience that Dragon Drop provides. It is an object containing "grabbed", "dropped", "reorder" and "cancel" functions allowing for custom live region announcements for all interactions that take place.

Each of these functions must return a string of announcement text which is added to the live region when the given action occurs. An additional benefit of utilizing these functions is that it supports localizing the live region messages.

To facilitate the announcements, the list item element in which the action took place upon and the array of items in the list are passed as the arguments respectively.

{
  announcement: {
    // grabbed is called when an item is picked up
    grabbed: (targetItem, items) => `${targetItem.innerText} grabbed`,
    // dropped is called when an item is dropped
    dropped: (targetItem, items) => `${targetItem.innerText} grabbed`,
    // reorder is called each time the order of the list is altered
    reorder: (targetItem, items) => {
      return `${targetItem.innerText} is now ${items.indexOf(targetItem) + 1} of ${items.length}`
    },
    // cancel is called when a reordering is cancelled (via escape key)
    cancel: () => 'The initial order has been restored, reordering cancelled'
  }
}

Help Text

It is absolutely vital that you provide help text which describes how to use the reorderable list. This is something Dragon Drop doesn’t do for you in order to remain less opinionated in how this text is made available to assistive technology. The recommended implementation is to use aria-describedby to associate the help text with the interactive items like so:

<p id="dragon-helper">Activate the reorder button and use the arrow keys to reorder the list or use your mouse to drag/reorder. Press escape to cancel the reordering.</p>
<ul id="dragon">
  <li>
<button aria-describedby="dragon-helper">Reorder Item 1</button>
<span>Item 1</span>
  </li>
  <li>
<button aria-describedby="dragon-helper">Reorder Item 2</button>
<span>Item 2</span>
  </li>
  <li>
<button aria-describedby="dragon-helper">Reorder Item 3</button>
<span>Item 3</span>
  </li>
</ul>

Dragon Drop On GitHub

The third version of Dragon Drop has recently been released. If you’re interested in using it, please refer to the Dragon Drop documentation on GitHub. A special thanks goes out to the creators of Dragula, the module Dragon Drop uses for mouse interaction, as well as Aaron Pearlman who designed the awesome logo!

The Future Of The Dragon

If drag-and-drop interaction is added to the WAI-ARIA technical specification in the future, the fact that Dragon Drop relies on non-standard interaction and live regions could change. I will continue performing tests ensuring it remains well supported by as many screen readers as possible and keep up to date with the latest ARIA spec. In addition, there are quite a few features in the pipeline including support for touch screen/mobile devices as well as multi-column lists (like sprint boards). Another feature that may be added in the future is a Dragon Drop React component.

See the Pen React Dragon Drop by Harris Schneiderman (@schne324) on CodePen.

See the Pen React Dragon Drop by Harris Schneiderman (@schne324) on CodePen.

Currently, Dragon Drop can be used with React, as displayed in the demo below, but it is not ideal because the DOM changes caused by the reordering of the list are not picked up by React which can cause unexpected behavior. I urge anyone who finds bugs in Dragon Drop, or even has ideas for features to create an issue on GitHub. All feedback and contribution is welcomed and greatly appreciated!

Further Reading

Smashing Editorial (rb, ra, yk, il, mrn)