- Live form: http://django-admin-tests.herokuapp.com/forms/example_form/p/
- Form fields definitions:
https://github.com/thibaudcolas/django_admin_tests/blob/main/django_admin_tests/forms.py
- Template:
https://github.com/thibaudcolas/django_admin_tests/blob/main/django_admin_tests/templates/django_admin_tests/example_form.html
== RadioSelect issues
1. The fields aren’t semantically grouped, only visually, so the grouping
isn’t apparent to assistive technlogies. It’s important that related
fields are grouped, and that the group has a label attached to it. If I
was auditing this professionally I would classify this as a fail for
[https://www.w3.org/WAI/WCAG21/Understanding/info-and-relationships.html
SC 1.3.1 - Info and Relationships] of WCAG 2.1, as the grouping and label
are somewhat there, just not done with the appropriate semantics.
2. Speaking of label – currently the group of fields is labeled by
preceding it with `<label for="id_radio_choice_required_0">Radio choice
required:</label>`. This overrides the label of the first field within the
group (and only labels the first field, not the whole group). This is a
fail for [https://www.w3.org/WAI/WCAG21/Understanding/labels-or-
instructions.html 3.3.2: Labels or Instructions] and/or SC 1.3.1.
3. Wrapping the fields each in their own list item makes the navigation
more tedious, as screen readers will first announce list items numbering,
and only then their content.
Here is a screenshot demonstrating the label issue with macOS VoiceOver’s
rotor. Note how the first field has the wrong label due to the markup.
radio-select-first-input-label.png
[[Image()]]
== CheckboxSelectMultiple issues
Almost identical to the above,
- Lack of a fieldset means no semantic grouping.
- The label isn’t associated with anything for this widget, so is less
problematic but no more correct.
- Wrapping the fields in list items also makes the form more verbose than
it should be (and the semantics issues are the same).
== Proposed solution
Essentially following [https://www.w3.org/TR/WCAG20-TECHS/H71 technique
H71] of WCAG. The ideal solution is identical for both:
1. Wrap the group of fields in a `fieldset`, including the group’s label,
the inputs, the help text, and any errors.
2. Use a `legend` as the first item in the `fieldset` for the group’s
label, rather than a `label`.
3. Replace the list markup with un-semantic div, or remove altogether. If
the list markup is needed for backwards compatibility, it could also use
`role="presentation"` so assistive technologies ignore the list & listitem
semantics, but I would only recommend that as a temporary workaround.
Here is sample markup to implement the above. The `div` aren’t needed,
I’ve only added them to preserve the vertical layout of the current
implementation:
{{{#!html
<fieldset>
<legend>Radio choice required:</legend>
<div><label for="id_radio_choice_required_0"><input type="radio"
name="radio_choice_required" value="one" required=""
id="id_radio_choice_required_0">
One</label></div>
<div><label for="id_radio_choice_required_1"><input type="radio"
name="radio_choice_required" value="two" required=""
id="id_radio_choice_required_1">
Two</label></div>
<div><label for="id_radio_choice_required_2"><input type="radio"
name="radio_choice_required" value="three" required=""
id="id_radio_choice_required_2">
Three</label></div>
<div><label for="id_radio_choice_required_3"><input type="radio"
name="radio_choice_required" value="four" required=""
id="id_radio_choice_required_3">
Four</label></div>
<span class="helptext">Help</span>
</fieldset>
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/32338>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
* Attachment "radio-select-first-input-label.png" added.
Screenshot of a RadioSelect widget, with DevTools open to see the markup,
and VoiceOver rotor menu showing the Form Controls’ labels
Old description:
New description:
== RadioSelect issues
[[Image(https://code.djangoproject.com/attachment/ticket/32338/radio-
select-first-input-label.png)]]
== CheckboxSelectMultiple issues
== Proposed solution
--
--
Ticket URL: <https://code.djangoproject.com/ticket/32338#comment:1>
Old description:
> In the Forms API, there are issues with the default rendering of fields
> [[Image(https://code.djangoproject.com/attachment/ticket/32338/radio-
> select-first-input-label.png)]]
New description:
== RadioSelect issues
[[Image(https://code.djangoproject.com/raw-attachment/ticket/32338/radio-
select-first-input-label.png)]]
== CheckboxSelectMultiple issues
Almost identical to the above,
- Lack of a fieldset means no semantic grouping.
- The label isn’t associated with anything for this widget, so is less
problematic but no more correct.
- Wrapping the fields in list items also makes the form more verbose than
it should be (and the semantics issues are the same).
== Proposed solution
Essentially following [https://www.w3.org/TR/WCAG20-TECHS/H71 technique
H71] of WCAG. The ideal solution is identical for both:
1. Wrap the group of fields in a `fieldset`, including the group’s label,
the inputs, the help text, and any errors.
2. Use a `legend` as the first item in the `fieldset` for the group’s
label, rather than a `label`.
3. Replace the list markup with un-semantic div, or remove altogether. If
the list markup is needed for backwards compatibility, it could also use
`role="presentation"` so assistive technologies ignore the list & listitem
semantics, but I would only recommend that as a temporary workaround.
Here is sample markup to implement the above. The `div` aren’t needed,
I’ve only added them to preserve the vertical layout of the current
implementation:
{{{
<fieldset>
<legend>Radio choice required:</legend>
<div><label for="id_radio_choice_required_0"><input type="radio"
name="radio_choice_required" value="one" required=""
id="id_radio_choice_required_0">
One</label></div>
<div><label for="id_radio_choice_required_1"><input type="radio"
name="radio_choice_required" value="two" required=""
id="id_radio_choice_required_1">
Two</label></div>
<div><label for="id_radio_choice_required_2"><input type="radio"
name="radio_choice_required" value="three" required=""
id="id_radio_choice_required_2">
Three</label></div>
<div><label for="id_radio_choice_required_3"><input type="radio"
name="radio_choice_required" value="four" required=""
id="id_radio_choice_required_3">
Four</label></div>
<span class="helptext">Help</span>
</fieldset>
}}}
--
--
Ticket URL: <https://code.djangoproject.com/ticket/32338#comment:2>
Old description:
> In the Forms API, there are issues with the default rendering of fields
> [[Image(https://code.djangoproject.com/raw-attachment/ticket/32338/radio-
> select-first-input-label.png)]]
>
> == CheckboxSelectMultiple issues
>
> Almost identical to the above,
>
> - Lack of a fieldset means no semantic grouping.
> - The label isn’t associated with anything for this widget, so is less
> problematic but no more correct.
> - Wrapping the fields in list items also makes the form more verbose than
> it should be (and the semantics issues are the same).
>
> == Proposed solution
>
> Essentially following [https://www.w3.org/TR/WCAG20-TECHS/H71 technique
> H71] of WCAG. The ideal solution is identical for both:
>
> 1. Wrap the group of fields in a `fieldset`, including the group’s label,
> the inputs, the help text, and any errors.
> 2. Use a `legend` as the first item in the `fieldset` for the group’s
> label, rather than a `label`.
> 3. Replace the list markup with un-semantic div, or remove altogether. If
> the list markup is needed for backwards compatibility, it could also use
> `role="presentation"` so assistive technologies ignore the list &
> listitem semantics, but I would only recommend that as a temporary
> workaround.
>
> Here is sample markup to implement the above. The `div` aren’t needed,
> I’ve only added them to preserve the vertical layout of the current
> implementation:
>
> {{{
> <fieldset>
> <legend>Radio choice required:</legend>
> <div><label for="id_radio_choice_required_0"><input type="radio"
> name="radio_choice_required" value="one" required=""
> id="id_radio_choice_required_0">
> One</label></div>
> <div><label for="id_radio_choice_required_1"><input type="radio"
> name="radio_choice_required" value="two" required=""
> id="id_radio_choice_required_1">
> Two</label></div>
> <div><label for="id_radio_choice_required_2"><input type="radio"
> name="radio_choice_required" value="three" required=""
> id="id_radio_choice_required_2">
> Three</label></div>
> <div><label for="id_radio_choice_required_3"><input type="radio"
> name="radio_choice_required" value="four" required=""
> id="id_radio_choice_required_3">
> Four</label></div>
> <span class="helptext">Help</span>
> </fieldset>
> }}}
New description:
== RadioSelect issues
[[Image(https://code.djangoproject.com/raw-attachment/ticket/32338/radio-
select-first-input-label.png)]]
== CheckboxSelectMultiple issues
Almost identical to the above,
- Lack of a fieldset means no semantic grouping.
- The label isn’t associated with anything for this widget, so is less
problematic but no more correct.
- Wrapping the fields in list items also makes the form more verbose than
it should be (and the semantics issues are the same).
== Documentation issues
Additionally to the above, there are a few occurences of custom
radio/checkbox markup in the documentation that will lead to the same
issues:
- https://docs.djangoproject.com/en/3.1/ref/forms/widgets/#radioselect
- https://docs.djangoproject.com/en/3.1/intro/tutorial04/#write-a-minimal-
form
== Proposed solution
Essentially following [https://www.w3.org/TR/WCAG20-TECHS/H71 technique
H71] of WCAG. The ideal solution is identical for both widgets, and also
for documentation code snippets:
1. Wrap the group of fields in a `fieldset`, including the group’s label,
the inputs, the help text, and any errors.
2. Use a `legend` as the first item in the `fieldset` for the group’s
label, rather than a `label`.
3. Replace the list markup with un-semantic div, or remove altogether. If
the list markup is needed for backwards compatibility, it could also use
`role="presentation"` so assistive technologies ignore the list & listitem
semantics, but I would only recommend that as a temporary workaround.
Here is sample markup to implement the above. The `div` aren’t needed,
I’ve only added them to preserve the vertical layout of the current
implementation:
{{{
<fieldset>
<legend>Radio choice required:</legend>
<div><label for="id_radio_choice_required_0"><input type="radio"
name="radio_choice_required" value="one" required=""
id="id_radio_choice_required_0">
One</label></div>
<div><label for="id_radio_choice_required_1"><input type="radio"
name="radio_choice_required" value="two" required=""
id="id_radio_choice_required_1">
Two</label></div>
<div><label for="id_radio_choice_required_2"><input type="radio"
name="radio_choice_required" value="three" required=""
id="id_radio_choice_required_2">
Three</label></div>
<div><label for="id_radio_choice_required_3"><input type="radio"
name="radio_choice_required" value="four" required=""
id="id_radio_choice_required_3">
Four</label></div>
<span class="helptext">Help</span>
</fieldset>
}}}
--
--
Ticket URL: <https://code.djangoproject.com/ticket/32338#comment:3>
Old description:
> In the Forms API, there are issues with the default rendering of fields
> [[Image(https://code.djangoproject.com/raw-attachment/ticket/32338/radio-
> select-first-input-label.png)]]
>
> == CheckboxSelectMultiple issues
>
> Almost identical to the above,
>
> - Lack of a fieldset means no semantic grouping.
> - The label isn’t associated with anything for this widget, so is less
> problematic but no more correct.
> - Wrapping the fields in list items also makes the form more verbose than
> it should be (and the semantics issues are the same).
>
> == Documentation issues
>
> Additionally to the above, there are a few occurences of custom
> radio/checkbox markup in the documentation that will lead to the same
> issues:
>
> - https://docs.djangoproject.com/en/3.1/ref/forms/widgets/#radioselect
> - https://docs.djangoproject.com/en/3.1/intro/tutorial04/#write-a
> -minimal-form
>
> == Proposed solution
>
> Essentially following [https://www.w3.org/TR/WCAG20-TECHS/H71 technique
> H71] of WCAG. The ideal solution is identical for both widgets, and also
> for documentation code snippets:
>
> 1. Wrap the group of fields in a `fieldset`, including the group’s label,
> the inputs, the help text, and any errors.
> 2. Use a `legend` as the first item in the `fieldset` for the group’s
> label, rather than a `label`.
> 3. Replace the list markup with un-semantic div, or remove altogether. If
> the list markup is needed for backwards compatibility, it could also use
> `role="presentation"` so assistive technologies ignore the list &
> listitem semantics, but I would only recommend that as a temporary
> workaround.
>
> Here is sample markup to implement the above. The `div` aren’t needed,
> I’ve only added them to preserve the vertical layout of the current
> implementation:
>
> {{{
> <fieldset>
> <legend>Radio choice required:</legend>
> <div><label for="id_radio_choice_required_0"><input type="radio"
> name="radio_choice_required" value="one" required=""
> id="id_radio_choice_required_0">
> One</label></div>
> <div><label for="id_radio_choice_required_1"><input type="radio"
> name="radio_choice_required" value="two" required=""
> id="id_radio_choice_required_1">
> Two</label></div>
> <div><label for="id_radio_choice_required_2"><input type="radio"
> name="radio_choice_required" value="three" required=""
> id="id_radio_choice_required_2">
> Three</label></div>
> <div><label for="id_radio_choice_required_3"><input type="radio"
> name="radio_choice_required" value="four" required=""
> id="id_radio_choice_required_3">
> Four</label></div>
> <span class="helptext">Help</span>
> </fieldset>
> }}}
New description:
In the Forms API, there are issues with the default rendering of fields
that rely on widgets based on multiple radio or checkbox inputs:
`RadioSelect` and `CheckboxSelectMultiple`. This is with the default
rendering of those widgets, and with the custom rendering examples in the
official documentation. Here is a form I set up for my testing of the
default rendering:
- Live form: http://django-admin-tests.herokuapp.com/forms/example_form/p/
- Form fields definitions:
https://github.com/thibaudcolas/django_admin_tests/blob/main/django_admin_tests/forms.py
- Template:
https://github.com/thibaudcolas/django_admin_tests/blob/main/django_admin_tests/templates/django_admin_tests/example_form.html
== RadioSelect issues
1. The fields aren’t semantically grouped, only visually, so the grouping
isn’t apparent to assistive technlogies. It’s important that related
fields are grouped, and that the group has a label attached to it. If I
was auditing this professionally I would classify this as a fail for
[https://www.w3.org/WAI/WCAG21/Understanding/info-and-relationships.html
SC 1.3.1 - Info and Relationships] of WCAG 2.1, as the grouping and label
are somewhat there, just not done with the appropriate semantics.
2. Speaking of label – currently the group of fields is labeled by
preceding it with `<label for="id_radio_choice_required_0">Radio choice
required:</label>`. This overrides the label of the first field within the
group (and only labels the first field, not the whole group). This is a
fail for [https://www.w3.org/WAI/WCAG21/Understanding/labels-or-
instructions.html 3.3.2: Labels or Instructions] and/or SC 1.3.1.
3. Wrapping the fields each in their own list item makes the navigation
more tedious, as screen readers will first announce list items numbering,
and only then their content.
Here is a screenshot demonstrating the label issue with macOS VoiceOver’s
rotor. Note how the first field has the wrong label due to the markup.
[[Image(https://code.djangoproject.com/raw-attachment/ticket/32338/radio-
select-first-input-label.png)]]
== CheckboxSelectMultiple issues
Almost identical to the above,
- Lack of a fieldset means no semantic grouping.
- The label isn’t associated with anything for this widget, so is less
problematic but no more correct.
- Wrapping the fields in list items also makes the form more verbose than
it should be (and the semantics issues are the same).
== Documentation issues
Additionally to the above, there are a few occurences of custom
radio/checkbox markup in the documentation that will lead to the same
issues:
- https://docs.djangoproject.com/en/3.1/ref/forms/widgets/#radioselect
- https://docs.djangoproject.com/en/3.1/intro/tutorial04/#write-a-minimal-
form
== Proposed solution
Essentially following [https://www.w3.org/TR/WCAG20-TECHS/H71 technique
H71] of WCAG. The ideal solution is identical for both widgets, and also
for documentation code snippets:
1. Wrap the group of fields in a `fieldset`, including the group’s label,
the inputs, the help text, and any errors.
2. Use a `legend` as the first item in the `fieldset` for the group’s
label, rather than a `label`.
3. Replace the list markup with un-semantic div, or remove altogether. If
the list markup is needed for backwards compatibility, it could also use
`role="presentation"` so assistive technologies ignore the list & listitem
semantics, but I would only recommend that as a temporary workaround.
Here is sample markup to implement the above. The `div` aren’t needed,
I’ve only added them to preserve the vertical layout of the current
implementation:
{{{
<fieldset>
<legend>Radio choice required:</legend>
<div><label for="id_radio_choice_required_0"><input type="radio"
name="radio_choice_required" value="one" required=""
id="id_radio_choice_required_0">
One</label></div>
<div><label for="id_radio_choice_required_1"><input type="radio"
name="radio_choice_required" value="two" required=""
id="id_radio_choice_required_1">
Two</label></div>
<div><label for="id_radio_choice_required_2"><input type="radio"
name="radio_choice_required" value="three" required=""
id="id_radio_choice_required_2">
Three</label></div>
<div><label for="id_radio_choice_required_3"><input type="radio"
name="radio_choice_required" value="four" required=""
id="id_radio_choice_required_3">
Four</label></div>
<span class="helptext">Help</span>
</fieldset>
}}}
--
--
Ticket URL: <https://code.djangoproject.com/ticket/32338#comment:4>
* stage: Unreviewed => Accepted
Comment:
Thanks for detailed report.
--
Ticket URL: <https://code.djangoproject.com/ticket/32338#comment:5>
* owner: nobody => jcoombes
* status: new => assigned
--
Ticket URL: <https://code.djangoproject.com/ticket/32338#comment:6>
Comment (by jcoombes):
21/01/21
Okay, so I can reproduce this bug.
I found the relevant template which fills the content of a relevant widget
at:
django/django/forms/templates/django/forms/widgets/multiple_input.html
[The optional version of this template is at
.../django/forms/input_option.html]
This template is used to render the html form using:
django/django/forms/forms.py
BaseForm.as_p,
BaseForm.as_ul,
BaseForm.as_table.
I got stuck today - as_p, as_ul and as_table call _html_output() with
different parameters.
Changing _html_output() is risky as it would be easy to break single-
choice form fields. The code is already quite abstracted.
Easier approach:
Create and document a new method as_fieldset() for this new use case.
Avoid _html_output() entirely.
Harder approach:
Rewrite _html_output() to include the html tags necessary to render
RadioSelect and CheckboxSelectMultiple accessibly.
Avoid breaking existing code.
I think I will take the easier approach, but I'm open to feedback on how
to do it the harder way without introducing regressions.
--
Ticket URL: <https://code.djangoproject.com/ticket/32338#comment:7>
Comment (by Claude Paroz):
Note you should be aware of current work in #31026 (template-based
rendering).
--
Ticket URL: <https://code.djangoproject.com/ticket/32338#comment:8>
* owner: jcoombes => (none)
* status: assigned => new
--
Ticket URL: <https://code.djangoproject.com/ticket/32338#comment:9>
* owner: (none) => David Smith
* status: new => assigned
--
Ticket URL: <https://code.djangoproject.com/ticket/32338#comment:10>
* has_patch: 0 => 1
--
Ticket URL: <https://code.djangoproject.com/ticket/32338#comment:11>
Comment (by Mariusz Felisiak <felisiak.mariusz@…>):
In [changeset:"32b366a86482b06db417aec67e42ca23d69da48a" 32b366a8]:
{{{
#!CommitTicketReference repository=""
revision="32b366a86482b06db417aec67e42ca23d69da48a"
[3.2.x] Refs #32338 -- Improved accessibility of RadioSelect examples in
docs.
Co-authored-by: Thibaud Colas <thibau...@gmail.com>
Backport of d8c17aa10c7f41e692fb6f5d0bf2fab7a90b9374 from main
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/32338#comment:12>
Comment (by Mariusz Felisiak <felisiak.mariusz@…>):
In [changeset:"d8c17aa10c7f41e692fb6f5d0bf2fab7a90b9374" d8c17aa]:
{{{
#!CommitTicketReference repository=""
revision="d8c17aa10c7f41e692fb6f5d0bf2fab7a90b9374"
Refs #32338 -- Improved accessibility of RadioSelect examples in docs.
Co-authored-by: Thibaud Colas <thibau...@gmail.com>
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/32338#comment:13>
Old description:
> In the Forms API, there are issues with the default rendering of fields
> that rely on widgets based on multiple radio or checkbox inputs:
> `RadioSelect` and `CheckboxSelectMultiple`. This is with the default
> rendering of those widgets, and with the custom rendering examples in the
> official documentation. Here is a form I set up for my testing of the
> default rendering:
>
> - Live form: http://django-admin-
> tests.herokuapp.com/forms/example_form/p/
> - Form fields definitions:
> https://github.com/thibaudcolas/django_admin_tests/blob/main/django_admin_tests/forms.py
> - Template:
> https://github.com/thibaudcolas/django_admin_tests/blob/main/django_admin_tests/templates/django_admin_tests/example_form.html
>
> == RadioSelect issues
>
> 1. The fields aren’t semantically grouped, only visually, so the grouping
> isn’t apparent to assistive technlogies. It’s important that related
> fields are grouped, and that the group has a label attached to it. If I
> was auditing this professionally I would classify this as a fail for
> [https://www.w3.org/WAI/WCAG21/Understanding/info-and-relationships.html
> SC 1.3.1 - Info and Relationships] of WCAG 2.1, as the grouping and label
> are somewhat there, just not done with the appropriate semantics.
> 2. Speaking of label – currently the group of fields is labeled by
> preceding it with `<label for="id_radio_choice_required_0">Radio choice
> required:</label>`. This overrides the label of the first field within
> the group (and only labels the first field, not the whole group). This is
> a fail for [https://www.w3.org/WAI/WCAG21/Understanding/labels-or-
> instructions.html 3.3.2: Labels or Instructions] and/or SC 1.3.1.
> 3. Wrapping the fields each in their own list item makes the navigation
> more tedious, as screen readers will first announce list items numbering,
> and only then their content.
>
> Here is a screenshot demonstrating the label issue with macOS VoiceOver’s
> rotor. Note how the first field has the wrong label due to the markup.
>
> [[Image(https://code.djangoproject.com/raw-attachment/ticket/32338/radio-
> select-first-input-label.png)]]
>
> == CheckboxSelectMultiple issues
>
> Almost identical to the above,
>
> - Lack of a fieldset means no semantic grouping.
> - The label isn’t associated with anything for this widget, so is less
> problematic but no more correct.
> - Wrapping the fields in list items also makes the form more verbose than
> it should be (and the semantics issues are the same).
>
> == Documentation issues
>
> Additionally to the above, there are a few occurences of custom
> radio/checkbox markup in the documentation that will lead to the same
> issues:
>
> - https://docs.djangoproject.com/en/3.1/ref/forms/widgets/#radioselect
> - https://docs.djangoproject.com/en/3.1/intro/tutorial04/#write-a
> -minimal-form
>
> == Proposed solution
>
> Essentially following [https://www.w3.org/TR/WCAG20-TECHS/H71 technique
> H71] of WCAG. The ideal solution is identical for both widgets, and also
> for documentation code snippets:
>
> 1. Wrap the group of fields in a `fieldset`, including the group’s label,
> the inputs, the help text, and any errors.
> 2. Use a `legend` as the first item in the `fieldset` for the group’s
> label, rather than a `label`.
> 3. Replace the list markup with un-semantic div, or remove altogether. If
> the list markup is needed for backwards compatibility, it could also use
> `role="presentation"` so assistive technologies ignore the list &
> listitem semantics, but I would only recommend that as a temporary
> workaround.
>
> Here is sample markup to implement the above. The `div` aren’t needed,
> I’ve only added them to preserve the vertical layout of the current
> implementation:
>
> {{{
> <fieldset>
> <legend>Radio choice required:</legend>
> <div><label for="id_radio_choice_required_0"><input type="radio"
> name="radio_choice_required" value="one" required=""
> id="id_radio_choice_required_0">
> One</label></div>
> <div><label for="id_radio_choice_required_1"><input type="radio"
> name="radio_choice_required" value="two" required=""
> id="id_radio_choice_required_1">
> Two</label></div>
> <div><label for="id_radio_choice_required_2"><input type="radio"
> name="radio_choice_required" value="three" required=""
> id="id_radio_choice_required_2">
> Three</label></div>
> <div><label for="id_radio_choice_required_3"><input type="radio"
> name="radio_choice_required" value="four" required=""
> id="id_radio_choice_required_3">
> Four</label></div>
> <span class="helptext">Help</span>
> </fieldset>
> }}}
New description:
In the Forms API, there are issues with the default rendering of fields
that rely on widgets based on multiple radio or checkbox inputs:
`RadioSelect` and `CheckboxSelectMultiple`. This is with the default
rendering of those widgets, and with the custom rendering examples in the
official documentation. Here is a form I set up for my testing of the
default rendering:
- Live form: http://django-admin-tests.herokuapp.com/forms/example_form/p/
- Form fields definitions:
https://github.com/thibaudcolas/django_admin_tests/blob/main/django_admin_tests/forms.py
- Template:
https://github.com/thibaudcolas/django_admin_tests/blob/main/django_admin_tests/templates/django_admin_tests/example_form.html
== RadioSelect issues
1. The fields aren’t semantically grouped, only visually, so the grouping
isn’t apparent to assistive technlogies. It’s important that related
fields are grouped, and that the group has a label attached to it. If I
was auditing this professionally I would classify this as a fail for
[https://www.w3.org/WAI/WCAG21/Understanding/info-and-relationships.html
SC 1.3.1 - Info and Relationships] of WCAG 2.1, as the grouping and label
are somewhat there, just not done with the appropriate semantics.
2. Speaking of label – currently the group of fields is labeled by
preceding it with `<label for="id_radio_choice_required_0">Radio choice
required:</label>`. This overrides the label of the first field within the
group (and only labels the first field, not the whole group). This is a
fail for [https://www.w3.org/WAI/WCAG21/Understanding/labels-or-
instructions.html 3.3.2: Labels or Instructions] and/or SC 1.3.1.
3. Wrapping the fields each in their own list item makes the navigation
more tedious, as screen readers will first announce list items numbering,
and only then their content.
Here is a screenshot demonstrating the label issue with macOS VoiceOver’s
rotor. Note how the first field has the wrong label due to the markup.
[[Image(https://code.djangoproject.com/raw-attachment/ticket/32338/radio-
select-first-input-label.png)]]
== CheckboxSelectMultiple issues
Almost identical to the above,
- Lack of a fieldset means no semantic grouping.
- The label isn’t associated with anything for this widget, so is less
problematic but no more correct.
- Wrapping the fields in list items also makes the form more verbose than
it should be (and the semantics issues are the same).
== Documentation issues (fixed in
d8c17aa10c7f41e692fb6f5d0bf2fab7a90b9374)
Additionally to the above, there are a few occurences of custom
radio/checkbox markup in the documentation that will lead to the same
issues:
- https://docs.djangoproject.com/en/3.1/ref/forms/widgets/#radioselect
- https://docs.djangoproject.com/en/3.1/intro/tutorial04/#write-a-minimal-
form
== Proposed solution
Essentially following [https://www.w3.org/TR/WCAG20-TECHS/H71 technique
H71] of WCAG. The ideal solution is identical for both widgets, and also
for documentation code snippets:
1. Wrap the group of fields in a `fieldset`, including the group’s label,
the inputs, the help text, and any errors.
2. Use a `legend` as the first item in the `fieldset` for the group’s
label, rather than a `label`.
3. Replace the list markup with un-semantic div, or remove altogether. If
the list markup is needed for backwards compatibility, it could also use
`role="presentation"` so assistive technologies ignore the list & listitem
semantics, but I would only recommend that as a temporary workaround.
Here is sample markup to implement the above. The `div` aren’t needed,
I’ve only added them to preserve the vertical layout of the current
implementation:
{{{
<fieldset>
<legend>Radio choice required:</legend>
<div><label for="id_radio_choice_required_0"><input type="radio"
name="radio_choice_required" value="one" required=""
id="id_radio_choice_required_0">
One</label></div>
<div><label for="id_radio_choice_required_1"><input type="radio"
name="radio_choice_required" value="two" required=""
id="id_radio_choice_required_1">
Two</label></div>
<div><label for="id_radio_choice_required_2"><input type="radio"
name="radio_choice_required" value="three" required=""
id="id_radio_choice_required_2">
Three</label></div>
<div><label for="id_radio_choice_required_3"><input type="radio"
name="radio_choice_required" value="four" required=""
id="id_radio_choice_required_3">
Four</label></div>
<span class="helptext">Help</span>
</fieldset>
}}}
--
--
Ticket URL: <https://code.djangoproject.com/ticket/32338#comment:14>
Comment (by Mariusz Felisiak <felisiak.mariusz@…>):
In [changeset:"b9e872b59329393f615c440c54f632a49ab05b78" b9e872b5]:
{{{
#!CommitTicketReference repository=""
revision="b9e872b59329393f615c440c54f632a49ab05b78"
Refs #32338 -- Removed 'for ="..."' from RadioSelect's <label>.
This improves accessibility for screen reader users.
Co-authored-by: Thibaud Colas <thibau...@gmail.com>
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/32338#comment:15>
Old description:
> In the Forms API, there are issues with the default rendering of fields
> that rely on widgets based on multiple radio or checkbox inputs:
> `RadioSelect` and `CheckboxSelectMultiple`. This is with the default
> rendering of those widgets, and with the custom rendering examples in the
> official documentation. Here is a form I set up for my testing of the
> default rendering:
>
> - Live form: http://django-admin-
> tests.herokuapp.com/forms/example_form/p/
> - Form fields definitions:
> https://github.com/thibaudcolas/django_admin_tests/blob/main/django_admin_tests/forms.py
> - Template:
> https://github.com/thibaudcolas/django_admin_tests/blob/main/django_admin_tests/templates/django_admin_tests/example_form.html
>
> == RadioSelect issues
>
> 1. The fields aren’t semantically grouped, only visually, so the grouping
> isn’t apparent to assistive technlogies. It’s important that related
> fields are grouped, and that the group has a label attached to it. If I
> was auditing this professionally I would classify this as a fail for
> [https://www.w3.org/WAI/WCAG21/Understanding/info-and-relationships.html
> SC 1.3.1 - Info and Relationships] of WCAG 2.1, as the grouping and label
> are somewhat there, just not done with the appropriate semantics.
> 2. Speaking of label – currently the group of fields is labeled by
> preceding it with `<label for="id_radio_choice_required_0">Radio choice
> required:</label>`. This overrides the label of the first field within
> the group (and only labels the first field, not the whole group). This is
> a fail for [https://www.w3.org/WAI/WCAG21/Understanding/labels-or-
> instructions.html 3.3.2: Labels or Instructions] and/or SC 1.3.1.
> 3. Wrapping the fields each in their own list item makes the navigation
> more tedious, as screen readers will first announce list items numbering,
> and only then their content.
>
> Here is a screenshot demonstrating the label issue with macOS VoiceOver’s
> rotor. Note how the first field has the wrong label due to the markup.
>
> [[Image(https://code.djangoproject.com/raw-attachment/ticket/32338/radio-
> select-first-input-label.png)]]
>
> == CheckboxSelectMultiple issues
>
> Almost identical to the above,
>
> - Lack of a fieldset means no semantic grouping.
> - The label isn’t associated with anything for this widget, so is less
> problematic but no more correct.
> - Wrapping the fields in list items also makes the form more verbose than
> it should be (and the semantics issues are the same).
>
> == Documentation issues (fixed in
> d8c17aa10c7f41e692fb6f5d0bf2fab7a90b9374)
>
> Additionally to the above, there are a few occurences of custom
> radio/checkbox markup in the documentation that will lead to the same
> issues:
>
> - https://docs.djangoproject.com/en/3.1/ref/forms/widgets/#radioselect
> - https://docs.djangoproject.com/en/3.1/intro/tutorial04/#write-a
> -minimal-form
>
> == Proposed solution
>
> Essentially following [https://www.w3.org/TR/WCAG20-TECHS/H71 technique
> H71] of WCAG. The ideal solution is identical for both widgets, and also
> for documentation code snippets:
>
> 1. Wrap the group of fields in a `fieldset`, including the group’s label,
> the inputs, the help text, and any errors.
> 2. Use a `legend` as the first item in the `fieldset` for the group’s
> label, rather than a `label`.
> 3. Replace the list markup with un-semantic div, or remove altogether. If
> the list markup is needed for backwards compatibility, it could also use
> `role="presentation"` so assistive technologies ignore the list &
> listitem semantics, but I would only recommend that as a temporary
> workaround.
>
> Here is sample markup to implement the above. The `div` aren’t needed,
> I’ve only added them to preserve the vertical layout of the current
> implementation:
>
> {{{
> <fieldset>
> <legend>Radio choice required:</legend>
> <div><label for="id_radio_choice_required_0"><input type="radio"
> name="radio_choice_required" value="one" required=""
> id="id_radio_choice_required_0">
> One</label></div>
> <div><label for="id_radio_choice_required_1"><input type="radio"
> name="radio_choice_required" value="two" required=""
> id="id_radio_choice_required_1">
> Two</label></div>
> <div><label for="id_radio_choice_required_2"><input type="radio"
> name="radio_choice_required" value="three" required=""
> id="id_radio_choice_required_2">
> Three</label></div>
> <div><label for="id_radio_choice_required_3"><input type="radio"
> name="radio_choice_required" value="four" required=""
> id="id_radio_choice_required_3">
> Four</label></div>
> <span class="helptext">Help</span>
> </fieldset>
> }}}
New description:
In the Forms API, there are issues with the default rendering of fields
that rely on widgets based on multiple radio or checkbox inputs:
`RadioSelect` and `CheckboxSelectMultiple`. This is with the default
rendering of those widgets, and with the custom rendering examples in the
official documentation. Here is a form I set up for my testing of the
default rendering:
- Live form: http://django-admin-tests.herokuapp.com/forms/example_form/p/
- Form fields definitions:
https://github.com/thibaudcolas/django_admin_tests/blob/main/django_admin_tests/forms.py
- Template:
https://github.com/thibaudcolas/django_admin_tests/blob/main/django_admin_tests/templates/django_admin_tests/example_form.html
== RadioSelect issues
1. The fields aren’t semantically grouped, only visually, so the grouping
isn’t apparent to assistive technlogies. It’s important that related
fields are grouped, and that the group has a label attached to it. If I
was auditing this professionally I would classify this as a fail for
[https://www.w3.org/WAI/WCAG21/Understanding/info-and-relationships.html
SC 1.3.1 - Info and Relationships] of WCAG 2.1, as the grouping and label
are somewhat there, just not done with the appropriate semantics.
2. (**fixed in b9e872b59329393f615c440c54f632a49ab05b78**) Speaking of
label – currently the group of fields is labeled by preceding it with
`<label for="id_radio_choice_required_0">Radio choice required:</label>`.
This overrides the label of the first field within the group (and only
labels the first field, not the whole group). This is a fail for
[https://www.w3.org/WAI/WCAG21/Understanding/labels-or-instructions.html
3.3.2: Labels or Instructions] and/or SC 1.3.1.
3. Wrapping the fields each in their own list item makes the navigation
more tedious, as screen readers will first announce list items numbering,
and only then their content.
Here is a screenshot demonstrating the label issue with macOS VoiceOver’s
rotor. Note how the first field has the wrong label due to the markup.
[[Image(https://code.djangoproject.com/raw-attachment/ticket/32338/radio-
select-first-input-label.png)]]
== CheckboxSelectMultiple issues
Almost identical to the above,
- Lack of a fieldset means no semantic grouping.
- The label isn’t associated with anything for this widget, so is less
problematic but no more correct.
- Wrapping the fields in list items also makes the form more verbose than
it should be (and the semantics issues are the same).
== Documentation issues (fixed in
d8c17aa10c7f41e692fb6f5d0bf2fab7a90b9374)
Additionally to the above, there are a few occurences of custom
radio/checkbox markup in the documentation that will lead to the same
issues:
- https://docs.djangoproject.com/en/3.1/ref/forms/widgets/#radioselect
- https://docs.djangoproject.com/en/3.1/intro/tutorial04/#write-a-minimal-
form
== Proposed solution
Essentially following [https://www.w3.org/TR/WCAG20-TECHS/H71 technique
H71] of WCAG. The ideal solution is identical for both widgets, and also
for documentation code snippets:
1. Wrap the group of fields in a `fieldset`, including the group’s label,
the inputs, the help text, and any errors.
2. Use a `legend` as the first item in the `fieldset` for the group’s
label, rather than a `label`.
3. Replace the list markup with un-semantic div, or remove altogether. If
the list markup is needed for backwards compatibility, it could also use
`role="presentation"` so assistive technologies ignore the list & listitem
semantics, but I would only recommend that as a temporary workaround.
Here is sample markup to implement the above. The `div` aren’t needed,
I’ve only added them to preserve the vertical layout of the current
implementation:
{{{
<fieldset>
<legend>Radio choice required:</legend>
<div><label for="id_radio_choice_required_0"><input type="radio"
name="radio_choice_required" value="one" required=""
id="id_radio_choice_required_0">
One</label></div>
<div><label for="id_radio_choice_required_1"><input type="radio"
name="radio_choice_required" value="two" required=""
id="id_radio_choice_required_1">
Two</label></div>
<div><label for="id_radio_choice_required_2"><input type="radio"
name="radio_choice_required" value="three" required=""
id="id_radio_choice_required_2">
Three</label></div>
<div><label for="id_radio_choice_required_3"><input type="radio"
name="radio_choice_required" value="four" required=""
id="id_radio_choice_required_3">
Four</label></div>
<span class="helptext">Help</span>
</fieldset>
}}}
--
--
Ticket URL: <https://code.djangoproject.com/ticket/32338#comment:16>
* stage: Accepted => Ready for checkin
--
Ticket URL: <https://code.djangoproject.com/ticket/32338#comment:17>
Comment (by Mariusz Felisiak <felisiak.mariusz@…>):
In [changeset:"5942ab5eb165ee2e759174e297148a40dd855920" 5942ab5]:
{{{
#!CommitTicketReference repository=""
revision="5942ab5eb165ee2e759174e297148a40dd855920"
Refs #32338 -- Made RadioSelect/CheckboxSelectMultiple render in <div>
tags.
This improves accessibility for screen reader users.
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/32338#comment:18>
* has_patch: 1 => 0
* stage: Ready for checkin => Accepted
Old description:
> In the Forms API, there are issues with the default rendering of fields
> that rely on widgets based on multiple radio or checkbox inputs:
> `RadioSelect` and `CheckboxSelectMultiple`. This is with the default
> rendering of those widgets, and with the custom rendering examples in the
> official documentation. Here is a form I set up for my testing of the
> default rendering:
>
> - Live form: http://django-admin-
> tests.herokuapp.com/forms/example_form/p/
> - Form fields definitions:
> https://github.com/thibaudcolas/django_admin_tests/blob/main/django_admin_tests/forms.py
> - Template:
> https://github.com/thibaudcolas/django_admin_tests/blob/main/django_admin_tests/templates/django_admin_tests/example_form.html
>
> == RadioSelect issues
>
> 1. The fields aren’t semantically grouped, only visually, so the grouping
> isn’t apparent to assistive technlogies. It’s important that related
> fields are grouped, and that the group has a label attached to it. If I
> was auditing this professionally I would classify this as a fail for
> [https://www.w3.org/WAI/WCAG21/Understanding/info-and-relationships.html
> SC 1.3.1 - Info and Relationships] of WCAG 2.1, as the grouping and label
> are somewhat there, just not done with the appropriate semantics.
> 2. (**fixed in b9e872b59329393f615c440c54f632a49ab05b78**) Speaking of
> label – currently the group of fields is labeled by preceding it with
> `<label for="id_radio_choice_required_0">Radio choice required:</label>`.
> This overrides the label of the first field within the group (and only
> labels the first field, not the whole group). This is a fail for
> [https://www.w3.org/WAI/WCAG21/Understanding/labels-or-instructions.html
> 3.3.2: Labels or Instructions] and/or SC 1.3.1.
> 3. Wrapping the fields each in their own list item makes the navigation
> more tedious, as screen readers will first announce list items numbering,
> and only then their content.
>
> Here is a screenshot demonstrating the label issue with macOS VoiceOver’s
> rotor. Note how the first field has the wrong label due to the markup.
>
> [[Image(https://code.djangoproject.com/raw-attachment/ticket/32338/radio-
> select-first-input-label.png)]]
>
> == CheckboxSelectMultiple issues
>
> Almost identical to the above,
>
> - Lack of a fieldset means no semantic grouping.
> - The label isn’t associated with anything for this widget, so is less
> problematic but no more correct.
> - Wrapping the fields in list items also makes the form more verbose than
> it should be (and the semantics issues are the same).
>
> == Documentation issues (fixed in
> d8c17aa10c7f41e692fb6f5d0bf2fab7a90b9374)
>
> Additionally to the above, there are a few occurences of custom
> radio/checkbox markup in the documentation that will lead to the same
> issues:
>
> - https://docs.djangoproject.com/en/3.1/ref/forms/widgets/#radioselect
> - https://docs.djangoproject.com/en/3.1/intro/tutorial04/#write-a
> -minimal-form
>
> == Proposed solution
>
> Essentially following [https://www.w3.org/TR/WCAG20-TECHS/H71 technique
> H71] of WCAG. The ideal solution is identical for both widgets, and also
> for documentation code snippets:
>
> 1. Wrap the group of fields in a `fieldset`, including the group’s label,
> the inputs, the help text, and any errors.
> 2. Use a `legend` as the first item in the `fieldset` for the group’s
> label, rather than a `label`.
> 3. Replace the list markup with un-semantic div, or remove altogether. If
> the list markup is needed for backwards compatibility, it could also use
> `role="presentation"` so assistive technologies ignore the list &
> listitem semantics, but I would only recommend that as a temporary
> workaround.
>
> Here is sample markup to implement the above. The `div` aren’t needed,
> I’ve only added them to preserve the vertical layout of the current
> implementation:
>
> {{{
> <fieldset>
> <legend>Radio choice required:</legend>
> <div><label for="id_radio_choice_required_0"><input type="radio"
> name="radio_choice_required" value="one" required=""
> id="id_radio_choice_required_0">
> One</label></div>
> <div><label for="id_radio_choice_required_1"><input type="radio"
> name="radio_choice_required" value="two" required=""
> id="id_radio_choice_required_1">
> Two</label></div>
> <div><label for="id_radio_choice_required_2"><input type="radio"
> name="radio_choice_required" value="three" required=""
> id="id_radio_choice_required_2">
> Three</label></div>
> <div><label for="id_radio_choice_required_3"><input type="radio"
> name="radio_choice_required" value="four" required=""
> id="id_radio_choice_required_3">
> Four</label></div>
> <span class="helptext">Help</span>
> </fieldset>
> }}}
New description:
In the Forms API, there are issues with the default rendering of fields
that rely on widgets based on multiple radio or checkbox inputs:
`RadioSelect` and `CheckboxSelectMultiple`. This is with the default
rendering of those widgets, and with the custom rendering examples in the
official documentation. Here is a form I set up for my testing of the
default rendering:
- Live form: http://django-admin-tests.herokuapp.com/forms/example_form/p/
- Form fields definitions:
https://github.com/thibaudcolas/django_admin_tests/blob/main/django_admin_tests/forms.py
- Template:
https://github.com/thibaudcolas/django_admin_tests/blob/main/django_admin_tests/templates/django_admin_tests/example_form.html
== RadioSelect issues
1. The fields aren’t semantically grouped, only visually, so the grouping
isn’t apparent to assistive technlogies. It’s important that related
fields are grouped, and that the group has a label attached to it. If I
was auditing this professionally I would classify this as a fail for
[https://www.w3.org/WAI/WCAG21/Understanding/info-and-relationships.html
SC 1.3.1 - Info and Relationships] of WCAG 2.1, as the grouping and label
are somewhat there, just not done with the appropriate semantics.
2. (**fixed in b9e872b59329393f615c440c54f632a49ab05b78**) Speaking of
label – currently the group of fields is labeled by preceding it with
`<label for="id_radio_choice_required_0">Radio choice required:</label>`.
This overrides the label of the first field within the group (and only
labels the first field, not the whole group). This is a fail for
[https://www.w3.org/WAI/WCAG21/Understanding/labels-or-instructions.html
3.3.2: Labels or Instructions] and/or SC 1.3.1.
3. (**fixed in 5942ab5eb165ee2e759174e297148a40dd855920**) Wrapping the
fields each in their own list item makes the navigation more tedious, as
screen readers will first announce list items numbering, and only then
their content.
Here is a screenshot demonstrating the label issue with macOS VoiceOver’s
rotor. Note how the first field has the wrong label due to the markup.
[[Image(https://code.djangoproject.com/raw-attachment/ticket/32338/radio-
select-first-input-label.png)]]
== CheckboxSelectMultiple issues
Almost identical to the above,
- Lack of a fieldset means no semantic grouping.
- The label isn’t associated with anything for this widget, so is less
problematic but no more correct.
- (**fixed in 5942ab5eb165ee2e759174e297148a40dd855920**) Wrapping the
fields in list items also makes the form more verbose than it should be
(and the semantics issues are the same).
== Documentation issues (fixed in
d8c17aa10c7f41e692fb6f5d0bf2fab7a90b9374)
Additionally to the above, there are a few occurences of custom
radio/checkbox markup in the documentation that will lead to the same
issues:
- https://docs.djangoproject.com/en/3.1/ref/forms/widgets/#radioselect
- https://docs.djangoproject.com/en/3.1/intro/tutorial04/#write-a-minimal-
form
== Proposed solution
Essentially following [https://www.w3.org/TR/WCAG20-TECHS/H71 technique
H71] of WCAG. The ideal solution is identical for both widgets, and also
for documentation code snippets:
1. Wrap the group of fields in a `fieldset`, including the group’s label,
the inputs, the help text, and any errors.
2. Use a `legend` as the first item in the `fieldset` for the group’s
label, rather than a `label`.
3. (**fixed in 5942ab5eb165ee2e759174e297148a40dd855920**) Replace the
list markup with un-semantic div, or remove altogether. If the list markup
is needed for backwards compatibility, it could also use
`role="presentation"` so assistive technologies ignore the list & listitem
semantics, but I would only recommend that as a temporary workaround.
Here is sample markup to implement the above. The `div` aren’t needed,
I’ve only added them to preserve the vertical layout of the current
implementation:
{{{
<fieldset>
<legend>Radio choice required:</legend>
<div><label for="id_radio_choice_required_0"><input type="radio"
name="radio_choice_required" value="one" required=""
id="id_radio_choice_required_0">
One</label></div>
<div><label for="id_radio_choice_required_1"><input type="radio"
name="radio_choice_required" value="two" required=""
id="id_radio_choice_required_1">
Two</label></div>
<div><label for="id_radio_choice_required_2"><input type="radio"
name="radio_choice_required" value="three" required=""
id="id_radio_choice_required_2">
Three</label></div>
<div><label for="id_radio_choice_required_3"><input type="radio"
name="radio_choice_required" value="four" required=""
id="id_radio_choice_required_3">
Four</label></div>
<span class="helptext">Help</span>
</fieldset>
}}}
--
--
Ticket URL: <https://code.djangoproject.com/ticket/32338#comment:19>
* has_patch: 0 => 1
--
Ticket URL: <https://code.djangoproject.com/ticket/32338#comment:20>
* needs_better_patch: 0 => 1
--
Ticket URL: <https://code.djangoproject.com/ticket/32338#comment:21>
* needs_better_patch: 1 => 0
* stage: Accepted => Ready for checkin
--
Ticket URL: <https://code.djangoproject.com/ticket/32338#comment:22>
Comment (by Mariusz Felisiak <felisiak.mariusz@…>):
In [changeset:"eba9a9b7f72995206af867600d6685b5405f172a" eba9a9b]:
{{{
#!CommitTicketReference repository=""
revision="eba9a9b7f72995206af867600d6685b5405f172a"
Refs #32338 -- Added Boundfield.legend_tag().
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/32338#comment:23>
* has_patch: 1 => 0
* stage: Ready for checkin => Accepted
--
Ticket URL: <https://code.djangoproject.com/ticket/32338#comment:24>
* has_patch: 0 => 1
--
Ticket URL: <https://code.djangoproject.com/ticket/32338#comment:25>
* needs_better_patch: 0 => 1
--
Ticket URL: <https://code.djangoproject.com/ticket/32338#comment:26>
* status: assigned => closed
* resolution: => fixed
Comment:
Fixed by ec5659382a5f5fc2daf0c87ccc89d0fb07534874 for #32339.
It may be possible to followup improving the `p`, `table`, and `ul`
templates but see https://code.djangoproject.com/ticket/32339#comment:3.
--
Ticket URL: <https://code.djangoproject.com/ticket/32338#comment:27>