Search topics
Sortable lists that move only by dragging, or in silence
A ranked list can be reordered only by dragging its rows by their grips, or its Move up and Move down buttons move a row without a word about where it went.
<li draggable="true"> The problem
People who can't drag can't change the order at all, and screen reader users who move a row with a button can't tell where it went.
After an order, a shop asks one question: "Rank what matters most when you order." Four rows, Price, Delivery speed, Easy returns and Reviews, each start with a grip of six dots, and the most important goes first. With a mouse, you press on Delivery speed, carry it above Price and let go, and the numbers on the left follow. Press Tab and focus goes straight past the list to the Send button: the rows are not in the Tab order, and the grips are drawings, not controls. On a phone, a finger pressed on a row and moved may scroll the page instead of carrying the row. Whoever can't drag sends the order the shop wrote, which is not an answer.
The shop's second build adds Move up and Move down buttons to every row. Now the keyboard can reach them, and pressing Move up on Delivery speed moves it to the top. A sighted user sees the row jump. A screen reader user hears "Move up, button, Delivery speed" again, the same words as before the press, because focus was put back on the same button. Nothing says the item is now first, or that it moved at all. To find out, they leave the button, read the list from the top, and come back.
The first build gives one way to change the order: a drag, which needs a press held down while the pointer travels. Success Criterion 2.5.7 asks for a way to do the same with single clicks or taps, unless dragging is essential, and it isn't for putting four words in order; with no such way, this is failure F108. The rows are list items with a decorative grip and nothing focusable, so the keyboard can't reorder them either, and 2.1.1 fails too. The grip only looks like a handle: a picture can't be pressed.
The second build passes both, and fails on what happens next. On screen, the press changes the numbers beside the rows: Delivery speed's from 2 to 1, and Price's from 1 to 2. Those numbers report the result of an action without taking focus, as the cart count does in the W3C's own example for Success Criterion 4.1.3, which asks that such a message can be presented to assistive technology through a role or a property, without focus moving to it. None of them is in a live region, which is how this guide reads failure F103. Some reviewers apply 4.1.3 only where a message in words is added to the page; either way, what can be observed is the same. Focus lands back on a button that keeps its name and its description, so a screen reader has nothing new to say, and nothing else on the page tells it where the row went. Moving an element in the DOM is silent, and it takes focus off the button inside it, which is why the script focuses it again.
The list is answered by the people who can see it whole and drag with ease. Everyone else either can't change the order or can't tell what order it is in now, and a ranking that goes in unchanged looks like a real answer to whoever reads the results.
3 of 10 groups affected
- No vision (affected)
- Low vision (not affected)
- Colour vision (not affected)
- No hearing (not affected)
- Hard of hearing (not affected)
- No speech (not affected)
- Motor (affected)
- Reach, strength (affected)
- Cognitive (not affected)
- Seizures (not affected)
- Limited manipulation Keyboard, switch and voice control users can't press and hold while moving, and a grip is not something Tab reaches, so in a list that only drags they can't change the order at all, and their answer goes in as the list was given.
- Without vision Screen reader users can't see where a dragged row would land. Where Move up and Move down exist, they press one and hear the same button again, not where the item went, so they read the whole list after every press to find it.
- Limited reach and strength People with tremors or weak hands let go one row too early or too late, and have to drag again, sometimes several times for one move.
Nothing in the markup is wrong: list items need no name, draggable="true" is valid HTML, and the Move buttons have names. A scan never drags a row or presses a button, so it never finds that the order can't change, or that a change is silent. EaseWeb's audit reports a list of draggable items with no button or select of their own as a lead to test by hand, and a list whose items can all be moved without dragging, with no live region outside the list or with the list inside one, as a second lead. Asked to make the list accessible, AI tools often add tabindex="0" and role="button" to the grips, which puts them in the Tab order with nothing for the keyboard to do, or put aria-live on the list, which reads the moved row's text and buttons again, with no position.
Try it
Put “Delivery speed” first. Drag it, then put the mouse aside and try with Tab.
The panel shows what a screen reader says. In Broken 1, can you move a row without dragging? In Broken 2, press Move up: does anything say where the row went?
Broken Can you fix this?
Rank what matters most when you order
- Price
- Delivery speed
- Easy returns
- Reviews
Screen reader says:
(Tab to a row, or drag one)
The fix
Keep the drag, add controls that move each row with a press, and say every row's new position in a status region.
- 1Every row moves up and down with single clicks, taps or key presses, no dragging.
- 2Every move says the item and its new position, once, without moving focus.
- 3Dragging stays as a shortcut, never as the only way to change the order.
Rows that move only by dragging
<h2 id="rank-title">Rank what matters most when you order</h2><ol id="ranking" aria-labelledby="rank-title"> <li draggable="true" data-item="speed"> <svg class="grip" aria-hidden="true">…</svg> <span id="item-speed">Delivery speed</span> </li> …</ol>Move up and Move down, with each move said
<h2 id="rank-title">Rank what matters most when you order</h2><ol id="ranking" aria-labelledby="rank-title"> <li draggable="true" data-item="speed"> <svg class="grip" aria-hidden="true">…</svg> <span id="item-speed">Delivery speed</span> <button type="button" data-move="-1" aria-describedby="item-speed">Move up</button> <button type="button" data-move="1" aria-describedby="item-speed">Move down</button> </li> …</ol><p id="rank-status" role="status"></p>Why this fix: Make a gesture a shortcut, never the only way
The only way to move a row is to drag it. The grip is hidden from assistive technology and can't take focus, and a list item can't be pressed.
Each button moves its row one place with a single click, tap or key press, so the list no longer needs a drag, and voice control users can say "Click Move up". The item's name is read as each button's description, so a screen reader says which row a button moves without changing its visible text. After the move, focus is back on the button and the status region, on the page from the start, says "Delivery speed moved to 1 of 4." The drop of a drag calls the same function, so a move with the mouse is announced in the same words. Press Move up again and the button stays where it is, rather than disappearing from under focus, and the status region says "Delivery speed is already first." This suits short lists: moving the sixth of six items to the top takes five presses.
Move up and Move down and the Position list both pass on their own, for keyboard, pointer and screen reader users. The buttons take one press per place and are the easiest to find and to say, so they suit short lists, up to five or six items. The Position list takes two steps for any move and stays the same however long the list is; choose it for longer lists, or where items often jump several places. Keyboard dragging is worth adding when keyboard users ask for a faster way, but only beside one of the others.
Keep the drag. It is fast for people who can do it, and 2.5.7 asks for an alternative, not a replacement. Put the drag on the row and the controls inside it, so a press on a button never starts a drag, and run the buttons on click, not on press, so a slip can be taken back.
Focus after the move. Moving an element in the DOM takes focus off the control inside it, so the script focuses it again. A framework that updates the list moves the row, or creates it afresh, and either way the control loses focus; focus it after the list has updated. Don't disable, hide or remove a button while it has focus, for example Move up when its item reaches the top: in Chrome, focus falls to the page and the person loses their place in the list. Keep the button and let it say "Delivery speed is already first."; if the design dims it, add aria-disabled="true" so it is read as unavailable and stays in the Tab order.
What the message says. Name the item and its new position, in the numbers the list shows: "Delivery speed moved to 1 of 4." Write it after focus has moved back, so the screen reader reads the button and then the message, and use role="status", which waits for it to finish, not role="alert". Don't read the whole new order, and never make the list itself a live region: a screen reader then reads the moved row's text and buttons again, and with aria-atomic="true" every row, without saying where the item went.
Grids and long lists. In a grid of photos that wraps, up and down mean earlier and later, so label the buttons "Move earlier" and "Move later", or offer the Position list. In a long list, Move to top and Move to bottom buttons save many presses.
Saving the order. If each move is saved at once, a move the server refuses must be said too, with the item put back: "Delivery speed could not be moved. It is still 2 of 4." If the order is sent with a form, keep the fields it sends in the same order as the rows.
Drag libraries. Many sortable libraries offer keyboard dragging and a live region of their own. Check the words they announce, which may name items by an id, and add buttons or a Position list for 2.5.7. Use either the library's region or your own for a move, not both, or every move is said twice.
Moving an item between lists or columns, rather than within one list, is covered in drag-and-drop boards that only a mouse can use, and announcing such a move in task boards that say nothing when a card moves.
Fixing this with an AI coding assistant? Get this guide as Markdown, or get every guide in your coding agent.
Design ideas
A ranking can look calm and still move without a drag.
Six ways to draw the same ranking. Try each with clicks, then put the mouse aside and use Tab: four move a row with single presses and say where it went, and two move only under a held pointer.
In the design file
- Draw every row’s move control at rest, not only on hover or after a long press.
- Draw the line that confirms a move under the list, such as “Delivery speed moved to 1 of 3.”
- Draw the ends of the list, with the top row’s Move up dimmed but still there, and what it says when pressed.
- Draw each move control with its focus ring, and the menu or numbers it opens.
- Draw the phone layout with every move control shown, each at least 24 by 24 pixels.
Passes
- Grip that opens a move menu The six-dot grip stays where the design put it, but it is a 32px button: a click, a tap or Enter opens Move to top, Move up, Move down and Move to bottom, and a drag can still drag. At rest the list looks exactly as drawn; the file needs the grip’s focus ring and the open menu.
Rule of thumb: Make a gesture a shortcut, never the only way
- Price
- Delivery speed
- Easy returns
Rank number you press to change The number at the start of each row is a 28px round button: press it and the places 1 to 3 open beside it, so any row reaches any place in two presses. It suits a ranking question, where the number is the answer people care about, and adds nothing at the row’s end.- Tap in order of importance No list to rearrange at all: people tap the options in the order that matters, and each takes a numbered badge; a second tap takes it out again. Every step is one press on a 44px-tall pill, it works the same on a phone, and the line under the options says the place each one was given.
- Price
- Delivery speed
- Easy returns
A pair of small arrows on each row Two 28 by 28 pixel chevrons stacked at the end of each row, quieter than buttons with words. Since they show no words, their names start with what they do, “Move up: Delivery speed”, which voice control users can say. The top row’s up arrow is dimmed but stays, and says the row is already first.
Fails
- Price
- Delivery speed
- Easy returns
Grip that appears on hoverWCAG 2.5.7 The calmest list of all: the grip fades in only when a row is pointed at, and dragging it is the only way to move the row. Tab finds nothing to press and a phone never shows the grip; draw it as a button that opens a menu, as the grip with a move menu does.Rule of thumb: Make a gesture a shortcut, never the only way
- Price
- Delivery speed
- Easy returns
Press and hold to liftWCAG 2.5.7 Familiar from phone apps: hold a row for half a second until it lifts, then drag it into place. The hold and the drag are the only way to move it, so keyboard, switch and voice control users can’t, and nothing on the row says it can be done at all.Rule of thumb: Make a gesture a shortcut, never the only way
Verify the fix
5 checks.
Test with: Keyboard, Mouse, Touch, Screen reader
Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the list component or its drag library changes.
Limits
This is a starting pattern, not a guarantee for every application. Nested lists, trees whose items can change level and lists that load more items as they scroll have their own needs, and are not covered here. Sortable libraries differ in how they handle focus, keys and announcements: test the complete journey with the assistive technology your users use.
This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.
Keep this fixed as your code changes.
Turn guides like this into rules your coding agents and CI follow, then have the result retested with a keyboard and screen readers.