Application forms: the Tags list has no accessible name with scripts off #625

Closed
opened 2026-10-01 10:48:28 +00:00 by tiagoagueda · 0 comments
Owner

Found on 2026-09-29 in a read-only review of postulo at 95348908d, reading partials/tags_field.html and rendering the application edit page through the test client.

Checked again on 2026-10-01 against main at 0a5cad587: still present. The script side is now at static/js/app.js:1777 and :1785 (aria-labelledby) and :1837-1838 (the native control hidden); the templates have not moved.

tags_field.html heads the native multiple select with <span class="label" id="labels-tags-heading">Tags</span>. A <span> labels nothing, and the select carries no aria-labelledby. Only app.js points its own chip control at that heading id, and it hides the native select while doing so (native.hidden = true). So with scripts off, which is exactly when the native select is what a person uses, the select has no accessible name. The company form's industries, the other user of the same pattern, use a <fieldset> and <legend> and do not have this problem (templates/jobs/company_form.html:44-46).

  • Where: templates/partials/tags_field.html:7-9; included by templates/applications/application_form.html:27 and templates/applications/application_intake.html:63. The script side is static/js/app.js:1544 and :1552 (aria-labelledby set on the elements it creates) and :1604-1605 (the native control hidden).
  • What happens: the edit page (applications:update) renders <span class="label" id="labels-tags-heading">Tags</span> and then <select name="tags" aria-describedby="id_tags_helptext" id="id_tags" multiple>, and nothing on the page says for="id_tags". With scripts off, a screen reader reaches a multiple-select list announced only by its help sentence; the visible word "Tags" above it is not associated.
  • Why it matters: WCAG 2.2 4.1.2 and 1.3.1 (level A), on the edit and record forms for an application, in the scripts-off mode the project promises meets AA. An axe scan with scripts off would report select-name.
  • Fix: make the heading a real label, keeping its id so the script's references still work: <label for="{{ form.tags.id_for_label }}" class="label" id="labels-{{ form.tags.html_name }}-heading">{{ form.tags.label }}</label>. Once app.js hides the select, the label points at a hidden control, which is harmless, and the chips keep their name through aria-labelledby.

Done when

  • A test in tests/test_labels.py renders applications:update and applications:create and asserts that the tags select has a <label for> pointing at it (or an aria-labelledby naming an element whose text is the field's label).
Found on 2026-09-29 in a read-only review of postulo at 95348908d, reading `partials/tags_field.html` and rendering the application edit page through the test client. Checked again on 2026-10-01 against `main` at `0a5cad587`: still present. The script side is now at `static/js/app.js:1777` and `:1785` (`aria-labelledby`) and `:1837-1838` (the native control hidden); the templates have not moved. `tags_field.html` heads the native multiple select with `<span class="label" id="labels-tags-heading">Tags</span>`. A `<span>` labels nothing, and the select carries no `aria-labelledby`. Only `app.js` points its own chip control at that heading id, and it hides the native select while doing so (`native.hidden = true`). So with scripts off, which is exactly when the native select is what a person uses, the select has no accessible name. The company form's industries, the other user of the same pattern, use a `<fieldset>` and `<legend>` and do not have this problem (`templates/jobs/company_form.html:44-46`). - **Where:** `templates/partials/tags_field.html:7-9`; included by `templates/applications/application_form.html:27` and `templates/applications/application_intake.html:63`. The script side is `static/js/app.js:1544` and `:1552` (`aria-labelledby` set on the elements it creates) and `:1604-1605` (the native control hidden). - **What happens:** the edit page (`applications:update`) renders `<span class="label" id="labels-tags-heading">Tags</span>` and then `<select name="tags" aria-describedby="id_tags_helptext" id="id_tags" multiple>`, and nothing on the page says `for="id_tags"`. With scripts off, a screen reader reaches a multiple-select list announced only by its help sentence; the visible word "Tags" above it is not associated. - **Why it matters:** WCAG 2.2 4.1.2 and 1.3.1 (level A), on the edit and record forms for an application, in the scripts-off mode the project promises meets AA. An axe scan with scripts off would report `select-name`. - **Fix:** make the heading a real label, keeping its id so the script's references still work: `<label for="{{ form.tags.id_for_label }}" class="label" id="labels-{{ form.tags.html_name }}-heading">{{ form.tags.label }}</label>`. Once `app.js` hides the select, the label points at a hidden control, which is harmless, and the chips keep their name through `aria-labelledby`. ## Done when - A test in `tests/test_labels.py` renders `applications:update` and `applications:create` and asserts that the `tags` select has a `<label for>` pointing at it (or an `aria-labelledby` naming an element whose text is the field's label).
tiagoagueda added this to the 0.5.0 milestone 2026-10-01 10:48:28 +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#625
No description provided.