Accessible Drag and Drop with Multiple Items

Originally published at: http://www.sitepoint.com/accessible-drag-drop/

In this article, I’d like to show you how to extend the capabilities of HTML5 drag and drop — so it can handle multiple elements, and support keyboard interaction, for sighted and screen reader users.

I’m going to assume that you already have a passing familiarity with the drag and drop API, but if not, have a look at this introductory article (ignoring the references to Modernizr, which you won’t need).

The basic approach to dragging multiple elements is actually pretty trivial — we simply need to remember the drag data for more than one element. We could do that using the dataTransfer object, or we can just use a separate array (which is what we’re going to do). So why write a whole article about it?

Why — because although the data is simple, the interface is rather more complex.

We’ll need to do some extra work to implement a pre-selection mechanism, and we’ll need to make it work from the keyboard (since native drag and drop doesn’t support this).

Note: This article won’t cover touch events, nor provide a polyfill for older browsers. Furthermore, the approach we take will only work within a single page, it won’t support dragging between windows. Although the drag and drop API does support this, there’s no straightforward way of making it keyboard accessible.

Basic Drag and Drop

So let’s start with a functional example which defines the basic drag and drop events, allowing a single element to be dragged with the mouse between two containers:

There’s a few things in there that might differ from other demos you’ve seen.

The first is the way it maintains an item reference for the element being dragged, rather than passing the element’s ID through the dataTransfer object (although we do have to pass something or the whole operation will fail in Firefox; it could be anything, so an empty string will do):

var item = null;

document.addEventListener('dragstart', function(e)
{
  item = e.target;
	
  e.dataTransfer.setData('text', '');

}, false);

This simplifies the demo by avoiding the need for the elements to have IDs, and would make it much easier to extend into a server-side application (like a CMS), where element IDs may not be easily knowable. This approach will also be the basis of multiple selection, where the item reference will become an items array.

The next significant thing is the omission of the event properties, effectAllowed and dropEffect. These can take a value such as "copy" or "move", and are supposed to control which actions are allowed and which cursor the browser will show. However, browser implementation is inconsistent, so there’s not much point including them.

Finally, note how the underlying HTML does not include any draggable attributes:

<ol data-draggable="target">
  <li data-draggable="item">Item 0</li>
  <li data-draggable="item">Item 1</li>
  <li data-draggable="item">Item 2</li>
  <li data-draggable="item">Item 3</li>
</ol>

Although most examples use these attributes in static HTML, I think that contradicts the principle of separation — since it allows the element to be dragged, but you can’t actually drop it anywhere without the accompanying JavaScript. So instead, I’ve used static data attributes to identify draggable elements, and then used scripting to apply the draggable attribute to browsers which pass feature detection.

This approach also provides the opportunity to exclude any broken implementations, for the cases where feature detection fails. Opera 12 or earlier is excluded using the window.opera test, because its implementation is quite buggy, and it’s not worth spending time on anymore.

Accessible Drag and Drop

Accessibility is a fundamental design principle, and it’s always easier to it get right when considered in those terms. So before we go any further with this demo, we must have a clear idea of the requirements for keyboard and screen reader accessibility.

The ARIA Authoring Practices has a section on drag and drop, which outlines the attributes we’ll need, and has guidelines on how the interactions should work. To summarize:

  1. Draggable elements are identified with aria-grabbed="false", and must be navigable with the keyboard.
  2. Provide a mechanism for the user to select which elements they want to drag, and the recommended keystroke is Space. When an element is selected for dragging, its aria-grabbed attribute is set to "true".
  3. Provide a mechanism for the user to indicate that they’ve finished making selections, and the recommended keystroke is Control+M.
  4. Target elements are then identified with aria-dropeffect, with a value that indicates which actions are allowed, such as "move" or "copy". At this point in the process, target elements must also be navigable with the keyboard.
  5. When the user arrives at a target element, provide a mechanism for them to perform the drop action, and the recommended keystroke is also Control+M.
  6. Users can cancel the entire operation at any time, by pressing the Escape key.
  7. When the action is completed or aborted, clean-up the interface by setting all aria-dropeffect attributes to "none" (or removing them), and all aria-grabbed attributes to "false".

Now the two ARIA attributes, aria-grabbed and aria-dropeffect, must be used according to specification. However, the events and interactions are simply recommendations, and we don’t have to follow them slavishly. Nonetheless, we should (and will) follow these recommendations as closely as possible, because they’re the closest thing we have to a normative reference.

And for the most part, I think it makes sense. But I do take issue with both uses of the Control+M keystroke. I’m not convinced that it’s really necessary to have an end-of-selection keystroke at all, and I don’t the keystroke itself is very intuitive for sighted keyboard users.

So I think the best approach is to supplement those recommendations:

  • We will implement the end-of-selection keystroke, but it won’t be required nor prevent further selection, it will simply be a shortcut for moving focus to the first drop target.
  • We will use Control+M for both keystrokes, but we’ll also allow the drop action to be triggered with the Enter key.

The guidelines also talk about implementing multiple selection using modifiers. The recommendation is to use Shift+Space for contiguous selection (i.e. selecting all items between two end points), and Control+Space for non-contiguous selection (i.e. selecting arbitrary items). Those are clearly the best modifiers to use … however, the Mac equivalent of Control+Space would be Command+Space, but that’s already bound to a system action and can’t be suppressed by JavaScript. Maybe we could use Control, but Mac users won’t be expecting that, since the Control key is mostly only used for triggering a right-click with a one-button mouse. The only remaining choice is Shift, but that’s earmarked for contiguous selection!

So for the sake of simplicity, and to side-step this problem, we’re not going to implement contiguous selection. The simplest thing we can do for now is to support all three modifiers for non-contiguous selection — Command+Space, Control+Space or Shift+Space are all treated the same way on every platform — then whatever a particular user can trigger, and whatever they think makes sense, is available.

Continue reading this article on SitePoint

Do people really use things like this with only keyboard?

I haven’t read trough all the text.
But by playing around with the examples, i have no idea how to drag&drop with my keyboard.

Is there some standard for what keys do what in situations like this?

I myself never use the keyboards for anything but to type in text, everything else is 100% mouse.
Mostly because i can never memorize any programs secret hidden handshakes,

Anyway, there seems to be no obvious way to figure it out, and i see no real visual indicators.

Look at the very last demo – that’s the final one, which has complete keyboard interaction. You might find it easier to use on a page of its own: http://jspro.brothercake.com/multidrag/demo4b.html

You Tab to items, press Space to select (or Modifier + Space for multiple selections), then Tab to a target container, then press Enter to drop.

Those are the standard keystrokes, described by the ARIA Authoring Guidelines. (Except for drop – the guidelines say to use Ctrl+M, and this script does also implement that – but I didn’t think that was very intuitive, so I added Enter as well).

As to whether people really use things like this with the keyboard – well in most cases, they don’t, because the interface doesn’t support keyboard navigation! If you build it, they will come :slight_smile:

But it’s not about providing for user preferences – it’s really not for the benefit of power users or people who prefer to use the keyboard (although it does have that benefit) – it’s for people who can ONLY use the keyboard. For that group of users, these keystrokes are the most intuitive and obvious choices, because they’re the keystrokes that are typically used for selecting and actuating things.

Aha, like for browsers for blind people etc?
I would not have guessed those keys in a meelion years, without some kind of manual.

Yeah exactly – for screenreader users, and for sighted keyboard users (eg. for people who have a hand tremor and can’t keep a mouse steady)

If you don’t typically use the keyboard for stuff, then it makes sense that you wouldn’t have an immediate sense of what keystrokes are obvious for certain things.

But frequent keyboard users are more likely to. For example, checkboxes are activated with Space, Enter is used to press buttons (and sometimes Space is used for that as well), so these are the logical choices.

Having said that, there is a case for saying that this kind of interaction could have instructions. Perhaps a popup tooltip that appears the first time you Tab to an item, that explains what the keystrokes are.

The keyboard functionality is awesome! Please implement touch next.

Touch dragging is complicated, because dragging is the same physical action as swiping and scrolling, so it would need some kind of pre-drag initiation – probably a longpress.

But yeah, that’s the plan :smile:

First of all, thank you! This is amazing!

I’m including it into a simple web page builder. That being said, I have very limited knowledge of javascript, and intend to pass it off to wiser javascript developers than I to get the whole page building experience going - I’m just building the static template and trying to include as much accessibility in as possible, in advance.

But in testing my rudimentary version, which totally works from a strictly keyboard/mouse perspective, I could only get VoiceOver to indicate when something was selected. Neither NVDA or JAWS seemed to pick up that I had clicked something. Do you have any advice on whether I need to use aria-selected somewhere to get verbal affirmation and would that even work? I admit, I’m fresh-off-the-boat on learning the many aria roles, but I’m trying! Also, do you know of any aria role that I can attach to the drop-zone as a verbal indicator that someone could drop something there?

Yeah screenreader support is rather patchy. The script is using the correct attributes – aria-grabbed for items and aria-dropeffect for drop target containers – but there’s not much communication of those states with different screenreaders.

For example, I tested Jaws 16 + Firefox and it would announce the items as draggable, but wouldn’t tell you that they were grabbed, or that a focused container was a drop target.

NVDA + Firefox was slightly better, announcing that an item has been grabbed (although ironically, not telling you that it’s a draggable item in the first place!), and also announcing when the item has been dropped (but not telling you where you can drop it!)

It’s hard to know how to improve things. On the one hand, the “correct” approach would be to stick to the current implementation and just wait for the screenreaders to support it properly.

But the useful approach would be to do whatever it takes to create a coherent interface. Maybe aria-selected could be helpful there – perhaps using aria-grabbed and aria-selected on each of the items would be a good approach. Maybe structural labels in the drop containers would provide useful information to help users identify them.

I’ll be doing a follow-up article to this in the next few weeks, so I’ll investigate some of these possibilities and take a view then :smile:

Thanks James for the wonderful contribution.
Could you please help me out with a couple of things. I would like to integrate this with php using ajax to update some fields in the database. Could you guide me where to put the ajax script and how to get the item/element ID and the ID of the container to pass using ajax?
Thanks a lot for your help.

Thank you! This is amazing!
I am new to javascript i need to use this in my web site and my html markup is different i have “p” and “A” tags in side each LI, and it is not dragging at all. Please tell where and what changes i have to make in code to work. here is my sample html markup.

    <ol data-draggable="target">
      <li data-draggable="item"><p>Item 0 <a href="#">my link</a></p></li>          
      <li data-draggable="item"><p>Item 1</p></li>
      <li data-draggable="item"><p>Item 2 <a href="#">my link</a></p></li>
      <li data-draggable="item"><p>Item 3</p></li>
   </ol>
  <ol data-draggable="target">
    <li data-draggable="item"><p>Item 4</p></li>
     <li data-draggable="item"><p>Item 5 <a href="#">my link</a></p></li>
  </ol>
  <ol data-draggable="target"> 
      <li data-draggable="item"><p>Item 6 <a href="#">my link</a></p></li>
      <li data-draggable="item"><p>Item 7</p></li>
  </ol>
   <ol data-draggable="target">
     <li data-draggable="item"><p>Item 8</p></li>
  </ol>
   <script src="demo4a.js" type="text/javascript"></script>

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.