Tags and industries: removing a chip with the keyboard drops focus to the start of the page #519

Closed
opened 2026-10-01 10:47:21 +00:00 by tiagoagueda · 0 comments
Owner

Found on 2026-09-29 in a read-only review of postulo at 95348908d, reading the label-chip control in static/js/app.js.

Checked again on 2026-10-01 against main at 0a5cad587: still present. The code is now at static/js/app.js:1679-1694 (the two remove callbacks), static/js/app.js:1657-1667 (drawLabels, down to list.textContent = "") and static/js/app.js:1711-1736 (chipFor); the two templates have not moved.

Each chip's remove button calls back into drawLabels, which empties the chip list and builds every chip again. The button that had focus is destroyed and nothing is focused in its place, so focus falls to the document. This is the shape #227 and #257 fixed for pagination, sorting and the reminder tick (a control that removes itself must hand focus on), applied nowhere in the chip control.

  • Where: static/js/app.js:1446-1461 (the two remove callbacks: value.set(false) or writeTyped(…), then sayIt(…), then drawLabels(box)); static/js/app.js:1424-1434 (drawLabels begins with list.textContent = ""); static/js/app.js:1478-1503 (chipFor builds a new button each time). There is no .focus() call on the removal path.
  • What happens: a keyboard or screen-reader user on an application's form or intake (templates/partials/tags_field.html, tags) or on a company form (templates/jobs/company_form.html:47-70, industries) tabs to a chip's remove button and presses Enter. The live region says the label was removed, and focus is now on <body>. The next Tab starts from the skip link, so removing three labels means walking past the header three times, and a screen reader is left with no position.
  • Why it matters: WCAG 2.2 SC 2.4.3, Focus Order, on two forms used every day. tests/e2e/test_labels.py removes chips by click and checks counts and announcements, not where focus goes; test_the_arrows_walk_between_labels shows the control is meant to be keyboard-operated.
  • Fix: before redrawing, note the removed chip's index. After drawLabels, focus the remove button now at that index, else the one before it, else the chip input when no chips remain. Only do so when focus was on the removed button, so a removal started by the pointer does not move focus unexpectedly.

Done when

  • A browser test in tests/e2e/test_labels.py focuses the first chip's remove button on a company with two industries, presses Enter, and asserts that the remaining chip's remove button is focused; a second press asserts that the chip input is focused. It fails today and passes after the fix.
Found on 2026-09-29 in a read-only review of postulo at 95348908d, reading the label-chip control in `static/js/app.js`. Checked again on 2026-10-01 against `main` at `0a5cad587`: still present. The code is now at `static/js/app.js:1679-1694` (the two remove callbacks), `static/js/app.js:1657-1667` (`drawLabels`, down to `list.textContent = ""`) and `static/js/app.js:1711-1736` (`chipFor`); the two templates have not moved. Each chip's remove button calls back into `drawLabels`, which empties the chip list and builds every chip again. The button that had focus is destroyed and nothing is focused in its place, so focus falls to the document. This is the shape #227 and #257 fixed for pagination, sorting and the reminder tick (a control that removes itself must hand focus on), applied nowhere in the chip control. - **Where:** `static/js/app.js:1446-1461` (the two remove callbacks: `value.set(false)` or `writeTyped(…)`, then `sayIt(…)`, then `drawLabels(box)`); `static/js/app.js:1424-1434` (`drawLabels` begins with `list.textContent = ""`); `static/js/app.js:1478-1503` (`chipFor` builds a new button each time). There is no `.focus()` call on the removal path. - **What happens:** a keyboard or screen-reader user on an application's form or intake (`templates/partials/tags_field.html`, tags) or on a company form (`templates/jobs/company_form.html:47-70`, industries) tabs to a chip's remove button and presses Enter. The live region says the label was removed, and focus is now on `<body>`. The next Tab starts from the skip link, so removing three labels means walking past the header three times, and a screen reader is left with no position. - **Why it matters:** WCAG 2.2 SC 2.4.3, Focus Order, on two forms used every day. `tests/e2e/test_labels.py` removes chips by click and checks counts and announcements, not where focus goes; `test_the_arrows_walk_between_labels` shows the control is meant to be keyboard-operated. - **Fix:** before redrawing, note the removed chip's index. After `drawLabels`, focus the remove button now at that index, else the one before it, else the chip input when no chips remain. Only do so when focus was on the removed button, so a removal started by the pointer does not move focus unexpectedly. ## Done when - A browser test in `tests/e2e/test_labels.py` focuses the first chip's remove button on a company with two industries, presses Enter, and asserts that the remaining chip's remove button is focused; a second press asserts that the chip input is focused. It fails today and passes after the fix.
tiagoagueda added this to the 0.5.0 milestone 2026-10-01 10:47:21 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Postulo/postulo#519
No description provided.