# Task boards that say nothing when a card moves

Source: https://easeweb.dev/learn/move-announcements
Topics: Drag and drop > A card moved to another list or column is announced in its new place (WCAG 4.1.3)
The fix: Keep one status region on the page from the start, outside the board, and after every move write one sentence into it that names the card and its new column. Either show it as a line under the board, or keep it visually hidden and leave the board as drawn. Don't make the board itself a live region.
Test with: screen reader, keyboard, mouse
References: https://www.w3.org/WAI/WCAG22/Understanding/status-messages.html https://www.w3.org/WAI/WCAG22/Techniques/failures/F103 https://www.w3.org/WAI/WCAG22/Techniques/aria/ARIA22 https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles/status_role https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-live

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.

## What happens

A team's task board has three columns, To do, Doing and Done, each with the number of its cards beside its heading. The board was built to work without dragging: every card has a button for each column it can go to, and the drag is still there for anyone who prefers it. Press Move to Done on "Write release notes" and the card jumps into Done, To do's count drops from 2 to 1, Done's rises from 0 to 1, and focus follows the card to its first button in the new column. A sighted user sees it land. A screen reader user hears "Move to To do, button, Write release notes": the name of the button focus landed on. Nothing says the card is now in Done, or that the move worked at all. The only clue is that the card's buttons now offer other columns, which they have to work out for themselves. To be sure, they leave the card, find the Done heading and read down its list, and then find their way back. A board is where work changes state, so this happens many times a day.

## Why it fails

"Write release notes moved to Done" is a status message: it reports the result of something the person did, and it doesn't take focus. On screen, that result is shown twice, by the card in its new column and by the counts in the headings. Success Criterion 4.1.3 asks that such a message can be presented to assistive technology through a role or a property, so that it is announced without focus moving to it. Here the counts are in no live region, and nothing else says where the card went, which is failure F103. Moving focus to the card doesn't make up for it: focus lands on a button that keeps its own name, "Move to To do", and the card's title is read as its description, so a screen reader says what the button would do next, not what just happened. The browser has nothing else to report. Moving an element in the DOM is silent: the page has to put the result into words for assistive technology, usually by writing them into a region it is already watching.

## Who is affected

The board works for people who can see it whole, because the card's new place is the confirmation. A board with many cards, or one that a narrow window turns into a single long column, makes that place hard to find for anyone who can't take it in at a glance.

## What automation and AI miss

A scan sees buttons with names, columns with headings and lists of cards, and all of it is valid. It never presses Move to Done, so it never sees the card move, and nothing in the markup says the counts report a result. EaseWeb's audit reports a list of draggable items that can all be moved without dragging as a lead to test by hand, when the page has no live region or when the list sits inside one. Asked to make the moves heard, AI assistants often put `aria-live="polite"` on the whole board or on each column. A screen reader then reads whatever text was added: the moved card's title and its buttons, and the two new counts as bare numbers, with no word for the column. With `aria-atomic="true"` added, it reads each changed region in full after each move: the whole board, or both columns, card by card. Only moving a card with a screen reader running shows what is said.

## Before: cards that move in silence

```html
<section class="column" data-column="todo">
  <h2>To do <span class="count">2</span></h2>
  <ul>
    <li data-card="notes" draggable="true">
      <span id="card-notes">Write release notes</span>
      <button type="button" data-to="doing" aria-describedby="card-notes">Move to Doing</button>
      <button type="button" data-to="done" aria-describedby="card-notes">Move to Done</button>
    </li>
    …
  </ul>
</section>
<!-- Doing and Done are the same: a heading with its count over a list of cards -->
```

```js
// The move buttons and the drop of a drag both call this, with the column's list.
function moveCard(card, list) {
  const from = card.closest('ul');
  list.append(card);
  updateCount(from);
  updateCount(list);
  updateButtons(card); // offer the columns it is not in now
  card.querySelector('button').focus();
}
```

The card moves, the counts change and focus follows the card, but none of it is in a live region, so a screen reader has only the focused button to read.

## After: a move line under the board

```html
<section class="column" data-column="todo">
  <h2>To do <span class="count">2</span></h2>
  <ul>
    <li data-card="notes" draggable="true">
      <span id="card-notes">Write release notes</span>
      <button type="button" data-to="doing" aria-describedby="card-notes">Move to Doing</button>
      <button type="button" data-to="done" aria-describedby="card-notes">Move to Done</button>
    </li>
    …
  </ul>
</section>
<!-- Doing and Done are the same: a heading with its count over a list of cards -->
<p id="board-status" role="status" class="board-status"></p>
```

```js
// The move buttons and the drop of a drag both call this, with the column's list.
function moveCard(card, list) {
  const from = card.closest('ul');
  list.append(card);
  updateCount(from);
  updateCount(list);
  updateButtons(card); // offer the columns it is not in now
  card.querySelector('button').focus();
  boardStatus.textContent = `${cardTitle(card)} moved to ${columnName(list)}.`;
}
```

The `p` with `role="status"` is in the HTML from the start, empty and outside the columns, so the browser is already watching it when the first move writes into it. After a press of Move to Done, a screen reader reads the newly focused button, then "Write release notes moved to Done." The sentence names the card and the column in the words on screen, and nothing else on the board is read. The drop of a drag calls the same function, so a move made with the mouse is announced in the same words. The line also shows sighted users the last move, which helps when a card has jumped out of view on a long board.

## After: a hidden status region, when the board stays as drawn

```html
<section class="column" data-column="todo">
  <h2>To do <span class="count">2</span></h2>
  <ul>
    <li data-card="notes" draggable="true">
      <span id="card-notes">Write release notes</span>
      <button type="button" data-to="doing" aria-describedby="card-notes">Move to Doing</button>
      <button type="button" data-to="done" aria-describedby="card-notes">Move to Done</button>
    </li>
    …
  </ul>
</section>
<!-- Doing and Done are the same: a heading with its count over a list of cards -->
<!-- Once, in the page layout, outside anything the board redraws -->
<div id="board-status" role="status" class="visually-hidden"></div>
```

```js
// The move buttons and the drop of a drag both call this, with the column's list.
function moveCard(card, list) {
  const from = card.closest('ul');
  list.append(card);
  updateCount(from);
  updateCount(list);
  updateButtons(card); // offer the columns it is not in now
  card.querySelector('button').focus();
  boardStatus.textContent = `${cardTitle(card)} moved to ${columnName(list)}.`;
}
```

The board looks exactly as it did, and a screen reader says the same sentence as with the line. The region lives in the layout, so a framework that redraws a column, or the whole board, after a move leaves it in place, and writing into it is a change the browser passes on to the screen reader. Keep it out of `display: none` and `hidden`; the visually hidden class keeps it in the accessibility tree.

## Which option to use

Both announce every move without the message needing focus, and they share the same code. The line under the board also tells sighted users what just happened, and gives a later Undo or an error a place to appear; choose it when the design has room for it. The hidden region leaves the board as drawn, for a design or a theme you can't change, and one region can serve every board and list on the site, as long as each message names its item and where it went.

## Implementation decisions

**One message, every way of moving.** Write the sentence in the one function that moves a card, and call it from every way in: the move buttons, a Move to list, keyboard dragging and the drop of a mouse drag. A board whose buttons are announced but whose drops are silent leaves out screen reader users who drag, and people who use a magnifier with speech.

**What to say.** Name the card and its new column in the words on screen: "Write release notes moved to Done." Add its position only when the move chose one or the column is sorted ("moved to Done, 2 of 3"). Don't read out the board's new state, the counts or the card's details; people who want them can read the column. A move the server refuses, or one a column's card limit blocks, needs words too: "Write release notes could not be moved. It is still in To do."

**Focus first, then the message.** Move focus to the card, then write the sentence. A polite message is meant to wait until the screen reader has finished reading the newly focused button. Written before the focus moves, it can be cut short by the reading of the new focus. [Drag-and-drop boards that only a mouse can use](/learn/drag-and-drop) covers where focus should go after a move.

**Not during the drag.** Say nothing while a card travels under the mouse; announce where it lands. Keyboard dragging is different, because a screen reader user can't see where the card is: there, each step is announced as it happens ("picked up", "over Doing", "dropped in Done", "put back in To do"), as the drag-and-drop guide's keyboard dragging does.

**Not a fix: the board as a live region.** Putting `aria-live` or `role="status"` on the board, a column or a list makes the screen reader read the content that changed, not the meaning of the change. Without `aria-atomic`, that is the added card's title and buttons and the counts as bare numbers; with it, the whole region, every card on the board or in both columns, after every move. The message is lost in the reading, or missing from it.

**The region must exist first.** Render the region once, outside any column component, keyed list or `{#if}` that the move redraws. A region that arrives along with its message is usually not announced; [status messages that are shown but never announced](/learn/status-messages) shows that failure in full.

**Drag libraries.** Some drag and drop libraries keep a live region of their own and announce drags by default. Check the words they use: dnd-kit's default messages, for example, name items and drop areas by their ids, so give it messages with the card's title and the column's name. Use either the library's region or your own for a move, not both, or every move is said twice.

**Politeness.** `role="status"` waits for the screen reader to finish, which suits a move. Don't use `role="alert"` or `aria-live="assertive"`; it can cut into the reading of the new focus.

## Verify the fix

1. Inspect the DOM before moving anything: one region with `role="status"` (or `aria-live="polite"`) is present and empty, is not hidden with `display: none` or `aria-hidden`, and sits outside the board's columns and lists; the board, the columns and the lists have no live region of their own.
2. With a screen reader running, move a card with its button: the newly focused control is read, then the card's name and its new column, once.
3. Move a card by dragging it with the mouse, and by every other way the board offers, such as a Move to list or keyboard dragging: each move gives the same kind of sentence when the card lands, and nothing is said while it travels.
4. Move one card back and forth between two columns several times: every move is announced, and the rest of the board is never read.

Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the board component or its drag library changes.

## Limits

Live regions behave differently across screen readers and browsers, and their timing beside a focus change differs most. This pattern covers moving a card between columns on one board. Making a card movable without dragging is covered in [drag-and-drop boards that only a mouse can use](/learn/drag-and-drop), and reordering items within one list, with their new position said, in [sortable lists that move only by dragging, or in silence](/learn/sortable-lists). Status messages in general are covered in [status messages that are shown but never announced](/learn/status-messages). Test your real board, with its drag library, its card limits and its narrow layout, with the screen readers your users use.

---

Get this guide in your coding agent: MCP https://easeweb.dev/mcp (setup at https://easeweb.dev/services/developers)
