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:
- Draggable elements are identified with
aria-grabbed="false"
, and must be navigable with the keyboard. - 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"
. - Provide a mechanism for the user to indicate that they’ve finished making selections, and the recommended keystroke is
Control+M
. - 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. - 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
. - Users can cancel the entire operation at any time, by pressing the
Escape
key. - When the action is completed or aborted, clean-up the interface by setting all
aria-dropeffect
attributes to"none"
(or removing them), and allaria-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 theEnter
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