<c-field>: a group of checkboxes gets a label attached to nothing, so the group has no name #624
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo#624
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found on 2026-09-29 in a read-only review of postulo at
95348908d, readingcotton/field.htmland rendering the Freeze and attach page through the test client.Checked again on 2026-10-01 against
mainat0a5cad587: still present.<c-field>draws two shapes: a single checkbox, and everything else as<label for="{{ field.id_for_label }}">followed by the widget. For a group widget (CheckboxSelectMultiple,RadioSelect) Django returns an emptyid_for_labelon purpose and setsuse_fieldset, expecting a<fieldset>and<legend>; it also gives the group's inputs noaria-describedbyand offers it asBoundField.aria_describedbyfor the fieldset instead. The component checks for none of this, so a group drawn through it gets<label for="">: the group has no accessible name, and its help text and errors are tied to nothing. The pages that draw groups by hand (api/token_list.html:39-40,applications/interview_form.html:32-33,applications/tag_form.html:43-60,jobs/company_form.html:44-46) already do it the right way.templates/cotton/field.html:36-50; used for groups bytemplates/documents/send.html:52-54, which loops every field ofSendDocumentsForm(documents/forms.py:302-318,uploadsandlinks, bothCheckboxSelectMultiple) through<c-field>.documents:sendrenders<label for="">Files you have already</label><div id="id_uploads">…and<p id="id_uploads_helptext">…</p>, with nothing pointing at either. When the person has no links, the emptylinksgroup falls into the single-checkbox branch instead (not field.field.widget.choicesis true for an empty queryset), which draws an empty<div id="id_links">beside a second<label for="">. A screen-reader user moving through the checkboxes hears each file's name, never "Files you have already" or "Links you pointed them at", and never either help sentence, so two groups of similar items run together.<c-field>inherits the same fault.cotton/field.html, branch onfield.use_fieldsetbefore the other two shapes: draw<fieldset class="field mb-4" {% if field.aria_describedby %}aria-describedby="{{ field.aria_describedby }}"{% endif %}>with<legend>{{ field.label }}</legend>, the widget and<c-field-feedback>, as the hand-drawn groups above do. Keep the single-checkbox branch for a lone checkbox only (test the widget class, not whether it has choices).Done when
tests/test_components.pyrenders aCheckboxSelectMultiplefield and aRadioSelectfield through<c-field>, with and without choices, and asserts a<fieldset>with a<legend>, anaria-describedbynaming the help text, and nofor=""anywhere.