I’ve been using the HTML Drag and Drop API (DNDAPI) a fair amount lately, which is a native way to build draggable interfaces. Despite some quirks, it works pretty well – making it easy for developers to create drag-and-drop functionality.

It also has good support: it’s available in the big four desktop browsers (Chrome, Edge, Firefox, Safari), and with iOS 15’s release in 2021 it’s now on both major mobile platforms (Android Chrome, iOS Safari). So its use is only going to grow.

But the DNDAPI has a big flaw: there’s no built-in accessibility features. This means screen reader users (and many others) won’t be able to use interfaces built with it without a lot of mindful, additional work from developers (which, let’s face it, most don’t do).

The challenge #

Until a baseline accessible experience gets baked into the DNDAPI, developers will need to roll out their own solutions – a messy endeavor with no best practices.

Drag-and-drop implementations can be quite different – so solutions to make them coherent for screen reader users are going to vary widely. Indicators, states, and feedback conveyed or implied visually now need to be communicated aurally. Or in some cases, like if you provide alternate controls, you might choose not to at all.

So can we even make these interfaces usable to screen reader users, or is it a fool's errand? I believe that we can, but in terms of "how", I don't know yet.

As part of a series, I'm going to attempt to learn how we can communicate these interactions to screen reader users. I'll start in this article by looking at ways to convey an item's draggability. Future articles will look at picking up and moving items, then I'll end by testing any promising options to see what actual users think.

Note:

Note: This article is not intended to present vetted patterns or offer a copy-paste solution. Any techniques are purposely simple and exist without wider context. The goal is to get an idea of support so we might better plan a usable screen reader experience to prototype and test.

What we're testing #

I'll be using a simple draggable element – a basic button. This doesn’t cover every element that developers use in drag-and-drop builds, like li or img, but it’s a good starting point. It will also be assumed that this implementation allows users to pick up an item by activating it.

In some tests, I provide my own drag information. In those cases, I keep it simple by using the text "draggable". In production, it may be clearer to use something like “click to grab” that matches the actual functionality better.

Lastly, I’ll test the draggable button in two situations – with and without a parent container that has a role="application". This role is sometimes applied to complex widgets that manually manage focus or use custom keyboard functionality. Its presence can change how screen readers announce an element, so I'll include it.

So the base markup will look something like this:

html
<div> <button>Apple</button> </div> <div role="application"> <button>Apple</button> </div>

Testing scope #

Desktop #

I’ll be testing four common desktop browser and screen reader combinations, which reflect the most commonly used primary screen reader for each browser, per the 2021 WebAIM Screen Reader User Survey #9:

  • JAWS (2021.2111.13) + Chrome (100) on Windows 10
  • NVDA (2021.3.5) + Firefox (99) on Windows 10
  • Narrator (2020) + Edge (100) on Windows 10
  • VoiceOver + Safari (15.4) on MacOS 12.3

The exception is pairing Edge with Narrator – while JAWS was listed as Edge’s most popular screen reader, I included Narrator since that same survey identified it as the fourth most-used when including secondary options. Since Microsoft develops both products and recommends using Narrator with Edge, we'll do just that.

We’ll also be navigating to our draggable elements a few ways. Buttons inside a generic wrapper will: 1) use the screen reader’s virtual cursor (arrow keys); and 2) use the tab key. While buttons within an application region will: 1) use a key event handler that moves focus on key press; and 2) use the tab key.

Mobile #

For mobile devices I’ll test in two environments, which reflect the top combination for each platform:

  • TalkBack (13.1) + Chrome (100) on Android 11, Samsung Galaxy A20s
  • VoiceOver + Safari on iOS 15.4.1, iPhone 13

In the mobile screen readers, I’ll navigate to our draggable elements by using horizontal swipes to move the virtual cursor.

Also, mobile screen readers don't have physical keyboards (unless you pair one), so the tab key is not used for navigation. Additionally, they don't react any differently to application regions. So the mobile results will only include one column.

The approaches
#

Let’s get into this article’s purpose: looking at ways we can indicate that an element is draggable/grabbable to a screen reader user. Again, we’re only looking at the information given when encountering the element.

draggable attribute #

Part of the DNDAPI, the draggable attribute makes any HTML element draggable by simply adding it with a value of true.

html
<div> <button draggable="true">Apple</button> </div> <div role="application"> <button draggable="true">Apple</button> </div>

You might think that the browser would also expose this to indicate an item’s draggable status. But you would be wrong:

Screen reader results: draggable attribute
Browser / screen reader button, via virtual cursor button, via tab button within application, via key event button within application, via tab
JAWS + Chrome "apple button" "apple button" "apple button" "apple button"
NVDA + Firefox "button apple" "apple button" "apple button" "apple button"
Narrator + Edge "button apple" "apple button" "apple button" "apple button"
VoiceOver + Safari "apple button" "apple button" "apple button" "apple button"
TalkBack + Android Chrome "apple button" N/A N/A N/A
VoiceOver + iOS Safari "apple button" N/A N/A N/A

As you can see, the draggable attribute doesn't pass anything to the accessibility tree for a screen reader to announce. This is expected, but it's good to confirm – we'll need to find a manual way to communicate that an element is draggable.

Static text #

The simplest method with the widest support would be to add our draggable information as regular text alongside the existing label.

html
<div> <button draggable="true">Apple (draggable)</button> </div> <div role="application"> <button draggable="true">Apple (draggable)</button> </div>

Although there may be attributes in the ARIA (Accessible Rich Internet Applications) specification that could help us, I want to test out-of-the-box features first before we dip into that.

Screen reader results: static text
Browser / screen reader button, via virtual cursor button, via tab button within application, via key event button within application, via tab
JAWS + Chrome "apple draggable button" "apple draggable button" "apple draggable button" "apple draggable button"
NVDA + Firefox "button apple draggable" "apple draggable button" "apple draggable button" "apple draggable button"
Narrator + Edge "button apple draggable” "apple draggable button" "apple draggable button" "apple draggable button"
VoiceOver + Safari "apple draggable button" "apple draggable button" "apple draggable button" "apple draggable button"
TalkBack + Android Chrome "apple draggable button" N/A N/A N/A
VoiceOver + iOS Safari "apple draggable button" N/A N/A N/A

Since the draggable information is part of the button text itself we don't have to worry about support. That, along with simplicity, is this approach's best advantage.

There's a few cons, though. Mainly, it results in duplicate info in the HTML to maintain. It also potentially adds visual clutter that, depending on the design, can make the interface appear busy or eat up valuable spacing.

Lastly, any users who also use voice commands may experience trouble activating the buttons, as their labels now contain the text "draggable".

Overall, this is a simple, valid approach.

"Visually hidden" text #

A variation of the previous idea, if we don't want the drag info to be visible we can actually hide it from view while keeping it present programmatically. We do this by creating a CSS "visually hidden" class and applying it to a span element:

html
<div> <button draggable="true"> Apple <span class="visuallyHidden">draggable</span> </button> </div> <div role="application"> <button draggable="true"> Apple <span class="visuallyHidden">draggable</span> </button> </div>
css
.visuallyHidden { clip: rect(0 0 0 0); clip-path: inset(50%); height: 1px; overflow: hidden; position: absolute; white-space: nowrap; width: 1px; }
Screen reader results: "visually hidden" text
Browser / screen reader button, via virtual cursor button, via tab button within application, via key event button within application, via tab
JAWS + Chrome "apple draggable button" "apple draggable button" "apple draggable button" "apple draggable button"
NVDA + Firefox "button apple draggable" "apple draggable button" "apple draggable button" "apple draggable button"
Narrator + Edge "button apple” and “draggable” (must navigate to both separately) "apple draggable button" "apple draggable button" "apple draggable button"
VoiceOver + Safari "apple draggable button" "apple draggable button" "apple draggable button" "apple draggable button"
TalkBack + Android Chrome "apple draggable button" N/A N/A N/A
VoiceOver + iOS Safari "apple draggable button" N/A N/A N/A

The support is great again, similar to the previous approach. Only Narrator seemed to trip up in browse mode, requiring me to navigate to the hidden span to hear it.

The approach is also still mostly simple, though requiring just a bit more markup. And it solves the visual clutter issue by hiding the drag information from view while still allowing it to be discovered by screen readers.

The other downsides still remain, though. There's still the issue of duplicate info in the HTML to maintain. And voice command users now have buttons whose canonical labels are different than their visible labels.

Despite that, this is a well-supported, solid approach.

aria-grabbed attribute #

The aria-grabbed attribute, an ARIA state indicator, is a way to programmatically convey that an element is draggable and whether it’s currently picked up or not.

html
<div> <button draggable="true" aria-grabbed="false">Apple</button> </div> <div role="application"> <button draggable="true" aria-grabbed="false">Apple</button> </div>

It sounds useful, but aria-grabbed was deprecated in ARIA 1.1 (the only meaty context I could find is this archived ARIA Working Group thread). Nevertheless, it hasn’t been completely removed from the spec yet since no alternative exists at the moment. So support is still out there in some capacity.

Screen reader results: aria-grabbed attribute
Browser / screen reader button, via virtual cursor button, via tab button within application, via key event button within application, via tab
JAWS + Chrome "apple button" "apple button" "apple button draggable" "apple button draggable"
NVDA + Firefox "button draggable apple" "apple button draggable" "apple button draggable" "apple button draggable"
Narrator + Edge "button apple" "apple button" "apple button" "apple button"
VoiceOver + Safari "apple button" "apple button" "apple button" "apple button"
TalkBack + Android Chrome "apple button" N/A N/A N/A
VoiceOver + iOS Safari "apple button" N/A N/A N/A

I found that only NVDA has wide support for aria-grabbed, with JAWS announcing it just inside of application regions.

Note: these screen readers communicate the aria-grabbed attribute by announcing "draggable", so their developers decided that this phrasing is sufficient to communicate draggability. While this may be too vague in some cases, it's at least some precedent at de facto messaging.

There's not much else to say about aria-grabbed other than if we want to support a range of screen readers this isn't a realistic solution.

aria-pressed attribute #

The aria-pressed attribute, another ARIA state indicator, is used to turn a button into a toggle with an on and off state. Screen readers will announce something like "Apple, toggle button, not pressed" when encountered.

This isn't enough to convey a draggable item on its own, at least not clearly. But what if we combined it with a static text approach, would a toggle make sense then?

html
<div> <button draggable="true" aria-pressed="false">Apple (draggable)</button> </div> <div role="application"> <button draggable="true" aria-pressed="false">Apple (draggable)</button> </div>

For this to work, the concept of a grabbable item being in a pressed or not-pressed state has to make sense, as users will hear announcements like "Apple, draggable, toggle button, not pressed".

Screen reader results: aria-pressed attribute
Browser / screen reader button, via virtual cursor button, via tab button within application, via key event button within application, via tab
JAWS + Chrome "apple draggable toggle button" "apple draggable toggle button" "apple draggable toggle button" "apple draggable toggle button"
NVDA + Firefox "toggle button not pressed apple draggable" "apple draggable toggle button not pressed" "apple draggable toggle button not pressed" "apple draggable toggle button not pressed"
Narrator + Edge "toggle button off apple draggable" "apple draggable toggle button off" "apple draggable toggle button off" "apple draggable toggle button off"
VoiceOver + Safari "apple draggable toggle button" "apple draggable toggle button" "apple draggable toggle button" "apple draggable toggle button"
TalkBack + Android Chrome "off apple draggable toggle button" N/A N/A N/A
VoiceOver + iOS Safari "apple draggable toggle button not pressed" N/A N/A N/A

The static text and aria-pressed attribute both have great support, so each screen reader announced everything correctly.

That's pretty much where the positives end with this approach. This method, like the static text, again has the duplicate content in the HTML to manage. The announcements are also an earful, and this is with a short message like "draggable". If something longer is used it could be ridiculous.

But my biggest issue is that pressing a toggle on/off doesn't map to the concept of picking up and putting down an item. Users are accustomed to toggles turning a setting on and off or showing and hiding an element, but here they'd need to understand that they're grabbing when pressing and releasing when un-pressing. It's confusing.

It's plausible that users could get past that and learn to use this modified toggle pattern. But with better options I'd prefer not to unless necessary.

aria-describedby attribute #

The aria-describedby ARIA attribute is used to point to an id of an element which provides additional information for another. This is usually announced by screen readers following an element’s name and role.

In our use case, that other element would contain the draggable information that a screen reader could then announce after the button’s label.

html
<div> <button draggable="true" aria-describedby="dragDesc">Apple</button> </div> <div role="application"> <button draggable="true" aria-describedby="dragDesc">Apple</button> </div> <div hidden id="dragDesc">draggable</div>
Screen reader results: aria-describedby attribute
Browser / screen reader button, via virtual cursor button, via tab button within application, via key event button within application, via tab
JAWS + Chrome "apple button" "apple button draggable" "apple button draggable" "apple button draggable"
NVDA + Firefox "button apple" "apple button draggable" "apple button draggable" "apple button draggable"
Narrator + Edge "button draggable apple" "apple button draggable" "apple button draggable" "apple button draggable"
VoiceOver + Safari "apple button [pause] draggable" "apple button [pause] draggable" "apple button [pause] draggable" "apple button [pause] draggable"
TalkBack + Android Chrome "apple button draggable" N/A N/A N/A
VoiceOver + iOS Safari "apple button [pause] draggable" N/A N/A N/A

The support is pretty good. The only hole was JAWS and NVDA not announcing the aria-describedby content when navigating with the virtual cursor.

Unlike some of the other approaches, this requires only one instance of the drag information, making it much easier to maintain. It's also a regular HTML element, so there won't be any issues with translation. Nor will voice control be affected by it since it's not part of a button's label.

Additionally, the drag text is announced after the button's label and role. I like the idea of placing the additional information last. If "draggable" were changed to "click to grab", it's more logical to hear "apple button, click to grab" than "apple click to grab button". A small, but helpful detail.

Despite the minor support blip, this method has several things going for it – making it a solid option in the right circumstance.

aria-label attribute #

The aria-label ARIA attribute is used to set a new accessible name for an element, overriding its text content.

The way it could be used here is to assign a new label that combines both the button text and drag information.

html
<div> <button draggable="true" aria-label="Apple (draggable)">Apple</button> </div> <div role="application"> <button draggable="true" aria-label="Apple (draggable)">Apple</button> </div>
Screen reader results: aria-label attribute
Browser / screen reader button, via virtual cursor button, via tab button within application, via key event button within application, via tab
JAWS + Chrome "apple draggable button" "apple draggable button" "apple draggable button" "apple draggable button"
NVDA + Firefox "button apple draggable" "apple draggable button" "apple draggable button" "apple draggable button"
Narrator + Edge "apple draggable button apple" "apple draggable button" "apple draggable button" "apple draggable button"
VoiceOver + Safari "apple draggable button" "apple draggable button" "apple draggable button" "apple draggable button"
TalkBack + Android Chrome "apple draggable button" N/A N/A N/A
VoiceOver + iOS Safari "apple draggable button" N/A N/A N/A

Support here for aria-label is really good, as every screen reader announces the text as expected.

For what it's worth, this is likely a misuse of aria-label, which is intended to set a clear accessible name and not to provide instructions or related info. Though a lot of these approaches are hacks in some form since there's no native way to do this.

An obvious downside to this method is that the HTML ends up with a lot of duplicate content. Each button contains the drag information as well as two instances of the label text, which is much harder to maintain.

Additionally, attributes like aria-label don't always get their content translated. So screen reader users who use a translation tool may experience a barrier there. The concatenated label will also affect voice command use.

Even though this approach has good support, I'd relegate it to the back burner for the reasons mentioned unless a specific use case for it arises.

aria-roledescription attribute #

The aria-roledescription ARIA attribute allows defining a custom role for an item to be announced as.

An item's role (button, checkbox, etc.) is essential for screen reader users to understand what it is and how it functions, so changing that is always a risk. Because of this, experts recommend not using aria-roledescription unless absolutely necessary. If you'd like to learn why, Adrian Roselli wrote this solid article on the pitfalls of aria-roledescription.

One of its few proper use cases is to add helpful context to a unique widget – which, in my opinion, a custom drag-and-drop module would qualify as.

I'm using it here to announce elements as a "draggable" item, but this could be anything relevant to our use case – like "draggable button", "moveable item", or whatever:

html
<div> <button draggable="true" aria-roledescription="draggable">Apple</button> </div> <div role="application"> <button draggable="true" aria-roledescription="draggable">Apple</button> </div>

Note that aria-roledescription affects only what an item is announced as, it doesn't change any functionality. So screen readers that support it will still treat our element as if it's a button, giving the user instructions on how to activate it. While those that don't support it simply fall back to calling it a button.

It's also worth noting that there's discussions on potentially changing the behavior for screen readers encountering aria-roledescription to announce both the new and original role. So in the example above, a screen reader would announce "apple draggable button".

Screen reader results: aria-roledescription attribute
Browser / screen reader button, via virtual cursor button, via tab button within application, via key event button within application, via tab
JAWS + Chrome "apple draggable" "apple draggable" "apple draggable" "apple draggable"
NVDA + Firefox "draggable apple" "apple draggable" "apple draggable" "apple draggable"
Narrator + Edge "apple draggable" "apple draggable" "apple draggable" "apple draggable"
VoiceOver + Safari "apple draggable" "apple draggable" "apple draggable" "apple draggable"
TalkBack + Android Chrome "apple draggable" N/A N/A N/A
VoiceOver + iOS Safari "apple button" N/A N/A N/A

Testing aria-roledescription shows that it has really good support, with nearly all screen reader + browser combinations tested announcing it correctly.

The question now becomes if users will be able to make sense of encountering a "draggable" or "draggable button" element. If we provide clear instructions, and even potentially combine it with secondary info in an aria-description or something, it's a definite possibility.

Only iOS VoiceOver/Safari failed to announce the altered role in favor of calling it a button. Again, providing clear instructions could help those users understand the functionality despite the aria-roledescription not being announced.

Another hesitancy with not just aria-roledescription, but all ARIA attributes, is translation concerns. Many of these attributes don't get translated when users enlist translation tools to scan and change the page to their spoken language.

I did a quick test using Google Translate, perhaps the largest page translation tool out there, and found that aria-roledescription did get translated. That's very promising! Though it likely needs further testing across browsers, translation tools, and languages before using it in a production environment.

I'm surprised at the level of support here, and that I'm not relying on hacks or dirtying up the DOM with hidden elements and such. So I'd like to move this to the next round with the others to test later in a full pattern.

Summary #

I've explored and tested the support of a number of methods we could use to designate a draggable item as such to a screen reader user.

Again, these experiments were done in a vacuum with no other context, so I can only use my best judgement at the moment. But the approaches that stand out most to me are the aria-describedby attribute, the static/"visually hidden" text, and aria-roledesription attribute.

aria-describedby attribute #

The aria-describedby attribute's support here is good. The only issue is with JAWS and NVDA while using the virtual cursor. If we utilize proper instructions, this concern could be eased a bit. And if our implementation uses an application region with custom key controls then it becomes moot.

Maintenance using this approach is easy, as we'll only have one instance of the drag information to worry about. That's a nice advantage over the static text.

That screen readers announce the aria-describedby content after the button label and role is ideal. Not only does the order seem more logical, but it makes it easy to skip if a user's heard it a bunch.

There's also no issues with activating the button using voice control, as we're using a separate HTML element that's not part of the canonical label. Translating its contents won't be an issue, either, as the instructions exist as an actual page element.

This approach just feels the cleanest to me out of all of the ideas I tried out. I'll be adding it to a prototype that I'll build and test in a future article.

Static and "visually hidden" text #

The static and "visually hidden" text methods are great in their simplicity. There's no support concerns since we're using regular text for our drag information. We also aren't dipping into ARIA usage, which works with the first ARIA principle of not using it unless necessary.

And because it's a standard text node there won't be any issues with translation tools or anything like that.

Maintenance is a concern, as we're adding the drag information inside each item. If something changes in the future we need to ensure it gets changed everywhere.

The static text also adds visual clutter – imagine a long reorderable list or a tight, mobile layout where each item contains that same drag information. Depending on your design, this may not be an issue. If so, the "visually hidden" approach can hide the repeated content visually while keeping it there for screen readers.

In addition, any users who rely on voice commands may experience issues with speaking the full label.

There's some valid pros and cons to this approach, but the simplicity and support make it attractive to me. I'll also be adding it to a more advanced prototype for later.

aria-roledescription attribute #

The aria-roledescription attribute has good support as well. The exception in my testing here was iOS VoiceOver with Safari where just the default button role was announced. As I've mentioned, using adequate instructions and a simplified layout could help to bridge that gap.

The main concern with the approach is that we're overwriting an element's role. But since there's no native draggable element or de facto way to communicate them to screen reader users, I feel that this can be a valid use case for doing so.

Maintenance concerns are that we'd need to include the attribute on every draggable element, so that's something to consider. But visually, there's no clutter or additional elements required.

Any voice control users won't be affected since we're not messing with the item's label, and at first glance translation tool support appears to be promising (though likely requires additional testing).

Certainly some valid pros and cons, but the level of support and that we don't need to rely on the accessible name or description is attractive for me to want to test this again as part of a full pattern.

Conclusion #

Regardless of the approach used, any well-designed interface should also include meaningful headings and informative text instructions. Pairing that with aids like helpful feedback, proper labeling, and a simple layout will go a long way toward making it understandable for users.

Again, this is only my opinion in this vacuum. The context and the specifics of each project will dictate what approach works best in a given situation.

It's also possible that I missed something obvious, am completely wrong, or that there's better ways out there. I'd love to hear any other ideas you've used or come up with.

Next, I'll move on to the following article in this series that will look at picking up and moving items. I hope to have it up within a few months. In the meantime, free to tweet or email me with comments or questions.

Update – 04/10/2023 #

A nice internet stranger emailed me asking for thoughts on a new drag-and-drop library called "DND Kit". DND Kit isn't build on the native DNDAPI, its drag-and-drop functionality is all custom built. It looks super impressive and has a ton of nice features.

That said, a very quick playaround revealed several things that make it unusable for screen reader users. So we're still in need of accessible drag-and-drop solutions.

Nevertheless, I did notice one interesting thing that DND Kit does – it uses the aria-roledescription property on draggable items to identify them as such to screen readers. That's something I hadn't considered when I first wrote this article, so I'm revisiting to test the option out alongside the others.