Refactoring a Sortable List For Keyboard Accessibility

Robby Macdonell
7 min readMar 16, 2021

--

Yesterday I spent a few hours working through an accessibility issue on Keep Posted — a drag and drop list of video clips couldn’t be rearranged without a mouse. Last month, I put in a lot of work improving the accessibility of the app, and now that clip list was starting to feel like a lingering black eye.

Rethinking a drag and drop interaction for keyboard-only use is interesting. There are a lot of ways to make the interaction feel natural for people using a mouse, using just a keyboard, or using other assistive technologies.** Accessibility shouldn’t feel like an afterthought, and I didn’t want to just bolt something on and call it good.

** Worth noting I’m not addressing screen readers here. That use case is important, but I don’t currently understand the WAI-ARIA guidelines well enough at this point to work that into this exploration.

Luckily, sortablejs — the drag and drop library I was already using — made the technical side of it fairly simple, freeing me up to think about the details of the interaction.

Here are a few of the things I needed to consider:

Control placement, aesthetics & behavior

The first question I had to think about was “how do we make it clear that this list is sortable with just the keyboard?” That’s a tough one. Packing too many visual cues into a small space may be distracting, especially if those cues are trying to communicate different things to different people. Someone should get the point that the list is sortable, but making them think too hard about all the different options for sorting is something I want to avoid.

Showing both interaction options clutters the UI

That said, it should be obvious that the list can be sorted, and it should be easily discoverable that you can use the keyboard to do it.

The solution I came to was to let the drag icon be an indicator that the element is able to be re-ordered at all — then that same space is used to show the up/down controls in the course of the normal focus order.

Sortable list item UI
Tabbing through a sortable list item

There are some compromises there to save space, for sure. But it feels like a fairly balanced solution.

Animation

One of the great things about drag and drop is that it feels real. You’re grabbing something, picking it up, and moving it to somewhere else.

If you remove the tactile affordances of drag and drop, the act of moving items around in a list can be really disorienting.

Like, can you tell what’s happening here?

Compare that to a version that adds animation

Sortablejs has a default animation with a configurable speed for drag and drop (for this one I settled on 250ms). When sorting the list programmatically, this animation is disabled by default, but in their latest release they added an option to use animation.

Focus handling

What should happen to keyboard focus when sorting list items? Sortablejs clobbers the original focus position after resorting the list, so there’s not really a default to work with.

It seems the most natural to me for the focus to ‘stick’ to the element that’s being acted on. If you activate the ‘move down’ action, your focus should stay put. This makes it quick and easy to move an item up and down through the list.

This works well until you reach the very top — or very bottom — of the list. Which brings us to the edge cases…

Edge cases (literally)

Sortablejs does it’s job well and I don’t have to think about weird edge cases with drag and drop. (mostly. read the bottom section for an exception)

But there are a few scenarios that need special attention for keyboard navigation.

Handling the start and end of lists

  • The first element in the sortable list should not have an “up” control
  • The last element in the sortable list should not have a “down” control

Since the reordering by keyboard can move an item up or down by a single place in the list, it raises the possibility that someone could try to just keep going outside the boundaries of the list.

Removing those controls is reasonable, and easy enough to do with CSS (see below for code examples). It removes an opportunity for confusion, and it takes two stops out of the tab-order, which makes keyboard navigation faster.

highlighting the useless up and down controls at the start and end of lists

But! It creates a tricky situation when thinking through the focus behavior. If the list tries to maintain focus on the ‘up’ control when moving to the top of the list, where the ‘up’ control is hidden, then the focus falls into a blank spot that’s unpredictable for the user.

The solution I came to was detecting if the focused control was still visible, and if not, simply switching focus to the other direction.

This keeps the focus on the list item that’s being acted on. Continuing to hit Enter after hitting the top of bottom of the list just switches the direction you’re moving.

Watch the first topic for an example.

moving a list item up and down with keyboard navigation

Handling lists that contain only one item

If a list only has a single item, that item is by definition both the first and last item on the list. So given the rules above, shouldn’t show either an up or down control.

But the visual cue is still there, which is confusing. So in that case, it makes sense to hide the drag icon altogether when there is just one item. This is fortunately really easy to do using the :only-child CSS selector.

Dealing with this scenario also fixes a really weird edge case for the mouse-based reordering that sortablejs doesn’t cover. More on that below.

What does the code look like?

Adding keyboard navigation to a list with sortablejs doesn’t take a whole lot of code. Basically, you just need:

  • HTML markup for the controls
  • A JavaScript function to move the items up or down one place in the list
  • CSS to handle the positioning of the controls, and hiding things when necessary.

HTML

This is the markup I used, trimmed up for clarity

<li class=”drag-handle”>
<img src="drag.svg" alt="Drag to reorder" aria-hidden="true" />
<div class=”keyboard-navigation”>
<a href=”#” data-direction=”up” onclick="moveUp">
<img src="up-arrow.svg", alt: "Move item up" />
</a>
<a href=”#” data-direction=”down” onclick="moveDown">
<img src="down-arrow.svg", alt: "Move item down" />
</a>
</div>
</li>

JAVASCRIPT

CSS

This is obviously going to vary a lot depending on the implementation. So here is the structural stuff I’m doing, with most of the look and feel styles stripped out.

A reminder that accessibility improvements often make products better for EVERYONE

The questions that came up while thinking through the keyboard-accessible reordering experience made me take a closer look at the original drag and drop interactions as well. I realized some of the same choices make the original mouse-based experience better as well.

For instance, let’s look at the edge case described above where there is just a single item in a list.

Sortablejs does a good job of not letting anything weird happen when dragging an item with nowhere to go. But you can still cause some bonkers behavior by dragging an item all the way outside the boundaries of the page.

Dragging a list item into the browser bar, which performs a web search
Wait… WHAT?!?! Dragging to the address bar does a Google search?

We can reduce confusion by eliminating the drag handles when there is only one item. It doesn’t completely fix the problem, but it sure helps!

This is a case where both the mouse-based and keyboard-based interaction got better.

This was a fun interaction to think through, and I’m pleased with where I ended up. Accessibility is a moving target, and there is more that I could do on this (like considering the screen-reader experience).

But while it’s difficult to be perfect when it comes to accessibility, a little bit of effort goes a long way and doing anything is better than doing nothing.

--

--

Robby Macdonell

Robby Macdonell is a designer and product leader who lives in Nashville, Tennessee.