#32338: Accessibility issues with Django forms RadioSelect and
CheckboxSelectMultiple
-------------------------------------+-------------------------------------
     Reporter:  Thibaud Colas        |                    Owner:  David
                                     |  Smith
         Type:  Bug                  |                   Status:  assigned
    Component:  Forms                |                  Version:  dev
     Severity:  Normal               |               Resolution:
     Keywords:  accessibility,       |             Triage Stage:  Accepted
  forms, wcag                        |
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  1
-------------------------------------+-------------------------------------
Changes (by Mariusz Felisiak):

 * 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>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/070.d5b66640b86a493bd2d047db13e30060%40djangoproject.com.

Reply via email to