{{{
<ul class="errorlist">
<li>This field is required.</li>
</ul>
<p>
<label for="id_duration_required">Duration required:</label>
<input type="text" name="duration_required" required=""
id="id_duration_required">
<span class="helptext">Help</span>
</p>
}}}
One problem for screen reader users is that the association between the
errors and the field, and between the help text and the field, is only
communicated visually. This is a failure of either WCAG 2.1 level A
[https://www.w3.org/WAI/WCAG21/Understanding/info-and-relationships.html
SC 1.3.1: Info and Relationships], or
[https://www.w3.org/WAI/WCAG21/Understanding/labels-or-instructions SC
3.3.2: Labels or Instructions]. More importantly, it just makes it harder
than necessary for screen reader users to make use of help text, and to
identify error messages.
The fix is relatively straightforward – using `aria-describedby`, as
documented in the (non-normative)
[https://www.w3.org/WAI/WCAG21/Techniques/aria/ARIA1.html ARIA1 Using the
aria-describedby property to provide a descriptive label for user
interface controls] technique. Here is another well-known accessibility-
oriented UI library that implements this technique: [https://design-
system.service.gov.uk/components/text-input/#error-messages GOV.UK design
system – text input with error message].
Here is what implementing `aria-describedby` would look like in the same
example as above:
{{{
<div class="errorlist" id="id_duration_required_errorlist">
<p>This field is required.</p>
</div>
<p>
<label for="id_duration_required">Duration required:</label>
<input type="text" name="duration_required" required=""
id="id_duration_required" aria-describedby="id_duration_required_errorlist
id_duration_required_helptext">
<span class="helptext" id="id_duration_required_helptext">Help</span>
</p>
}}}
We have additional `id` attributes, `aria-describedby`, and `errorlist` is
no longer a `<ul>`. Result in VoiceOver:
Screen recording of the VoiceOver text-to-speech output, announcing the
field label, then error message, then help text.
Unfortunately I tried to have this with the `errorlist` kept as a `ul`,
but it wasn’t announced by VoiceOver. I haven’t heard of this limitation
before so am not sure why that might be the case – I’d appreciate others
taking a look if possible.
--
Ticket URL: <https://code.djangoproject.com/ticket/32819>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
* Attachment "email-required-ariadescribedby.gif" added.
Screen recording of the VoiceOver text-to-speech output, announcing the
field label, then error message, then help text.
--
Old description:
New description:
[[Image(https://code.djangoproject.com/raw-attachment/ticket/32819/email-
required-ariadescribedby.gif)]]
Unfortunately I tried to have this with the `errorlist` kept as a `ul`,
but it wasn’t announced by VoiceOver. I haven’t heard of this limitation
before so am not sure why that might be the case – I’d appreciate others
taking a look if possible.
--
--
Ticket URL: <https://code.djangoproject.com/ticket/32819#comment:1>
* component: contrib.admin => Forms
--
Ticket URL: <https://code.djangoproject.com/ticket/32819#comment:2>
* stage: Unreviewed => Accepted
Comment:
Thanks. Ideally, we should avoid changing `<ul>` to `<div>`. Maybe `<ul>`
could be wrapped by `<div>`.
--
Ticket URL: <https://code.djangoproject.com/ticket/32819#comment:3>
* owner: nobody => Hasan Ramezani
* status: new => assigned
Comment:
I created a draft [https://github.com/django/django/pull/14515 PR].
@Mariusz, Could you please check it and let me know if I choose the right
direction to fix the problem? If so, I can continue with test adjustment.
@Thibaud, It would be great if you can if it will produce your desired
output.
--
Ticket URL: <https://code.djangoproject.com/ticket/32819#comment:4>
* owner: Hasan Ramezani => (none)
* status: assigned => new
--
Ticket URL: <https://code.djangoproject.com/ticket/32819#comment:5>
* owner: (none) => PriyanshuGarg26
* status: new => assigned
--
Ticket URL: <https://code.djangoproject.com/ticket/32819#comment:6>
* owner: PriyanshuGarg26 => Nimra
--
Ticket URL: <https://code.djangoproject.com/ticket/32819#comment:7>
* needs_better_patch: 0 => 1
* has_patch: 0 => 1
Comment:
[https://github.com/django/django/pull/16185 PR]
--
Ticket URL: <https://code.djangoproject.com/ticket/32819#comment:8>
* owner: Nimra => gregorjerse
--
Ticket URL: <https://code.djangoproject.com/ticket/32819#comment:9>
* needs_better_patch: 1 => 0
--
Ticket URL: <https://code.djangoproject.com/ticket/32819#comment:10>
Comment (by Mariusz Felisiak):
[https://github.com/django/django/pull/16920 PR]
--
Ticket URL: <https://code.djangoproject.com/ticket/32819#comment:11>
* version: 3.2 => dev
--
Ticket URL: <https://code.djangoproject.com/ticket/32819#comment:12>
* needs_better_patch: 0 => 1
Comment:
Note that the PR focuses on solving this issue for `help_text`.
--
Ticket URL: <https://code.djangoproject.com/ticket/32819#comment:13>
* needs_better_patch: 1 => 0
* stage: Accepted => Ready for checkin
--
Ticket URL: <https://code.djangoproject.com/ticket/32819#comment:14>
* status: assigned => closed
* resolution: => fixed
Comment:
In [changeset:"966ecdd482167f3f6b08b00f484936c837751cb9" 966ecdd4]:
{{{
#!CommitTicketReference repository=""
revision="966ecdd482167f3f6b08b00f484936c837751cb9"
Fixed #32819 -- Established relationship between form fields and their
help text.
Thanks Nimra for the initial patch.
Thanks Natalia Bidart, Thibaud Colas, David Smith, and Mariusz Felisiak
for reviews.
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/32819#comment:15>
* status: closed => new
* resolution: fixed =>
Comment:
Reopening as the errors case still requires fixing.
--
Ticket URL: <https://code.djangoproject.com/ticket/32819#comment:16>
* has_patch: 1 => 0
* stage: Ready for checkin => Accepted
--
Ticket URL: <https://code.djangoproject.com/ticket/32819#comment:17>
* owner: Gregor Jerše => David Smith
* status: new => assigned
* has_patch: 0 => 1
--
Ticket URL: <https://code.djangoproject.com/ticket/32819#comment:18>
Comment (by Mariusz Felisiak <felisiak.mariusz@…>):
In [changeset:"292f1ea90f90ff140617299a25884c8fda24aa64" 292f1ea9]:
{{{
#!CommitTicketReference repository=""
revision="292f1ea90f90ff140617299a25884c8fda24aa64"
Refs #32819 -- Used auto_id instead of id_for_label as unique identifier
for the field.
`id_for_label` is blank for widgets with multiple inputs such as radios
and multiple checkboxes. Therefore , `help_text` for fields using these
widgets cannot currently be associated using `aria-describedby`.
`id_for_label` is being used as a guard to avoid incorrectly adding
`aria-describedby` to those widgets.
This change uses `auto_id` as the unique identified for the fields
`help_text`. A guard is added to avoid incorrectly adding
`aria-describedby` to inputs by checking the widget's `use_fieldset`
attribute. Fields rendered in a `<fieldset>` should have
`aria-describedby` added to the `<fieldset>` and not every `<input>`.
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/32819#comment:19>
Comment (by Mariusz Felisiak <felisiak.mariusz@…>):
In [changeset:"7f0275d8cb6a590fa5e157cadeba776e9a790121" 7f0275d8]:
{{{
#!CommitTicketReference repository=""
revision="7f0275d8cb6a590fa5e157cadeba776e9a790121"
[5.0.x] Refs #32819 -- Used auto_id instead of id_for_label as unique
identifier for the field.
`id_for_label` is blank for widgets with multiple inputs such as radios
and multiple checkboxes. Therefore , `help_text` for fields using these
widgets cannot currently be associated using `aria-describedby`.
`id_for_label` is being used as a guard to avoid incorrectly adding
`aria-describedby` to those widgets.
This change uses `auto_id` as the unique identified for the fields
`help_text`. A guard is added to avoid incorrectly adding
`aria-describedby` to inputs by checking the widget's `use_fieldset`
attribute. Fields rendered in a `<fieldset>` should have
`aria-describedby` added to the `<fieldset>` and not every `<input>`.
Backport of 292f1ea90f90ff140617299a25884c8fda24aa64 from main
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/32819#comment:20>
* stage: Accepted => Ready for checkin
--
Ticket URL: <https://code.djangoproject.com/ticket/32819#comment:21>
Comment (by Mariusz Felisiak <felisiak.mariusz@…>):
In [changeset:"557fa51837a57534f8ca486133a412547a98a37e" 557fa51]:
{{{
#!CommitTicketReference repository=""
revision="557fa51837a57534f8ca486133a412547a98a37e"
Refs #32819 -- Added aria-describedby test for widgets with custom id.
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/32819#comment:22>
Comment (by Mariusz Felisiak <felisiak.mariusz@…>):
In [changeset:"eec7e9ba894a7815d289ba7446eeead488fe0e33" eec7e9ba]:
{{{
#!CommitTicketReference repository=""
revision="eec7e9ba894a7815d289ba7446eeead488fe0e33"
Refs #32819 -- Established relationship between form fieldsets and their
help text.
This adds aria-describedby for widgets rendered in a fieldset such as
radios. aria-describedby for these widgets is added to the <fieldset>
element rather than each <input>.
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/32819#comment:23>
* has_patch: 1 => 0
* stage: Ready for checkin => Accepted
--
Ticket URL: <https://code.djangoproject.com/ticket/32819#comment:24>
* has_patch: 0 => 1
--
Ticket URL: <https://code.djangoproject.com/ticket/32819#comment:25>
* needs_better_patch: 0 => 1
Comment:
Setting as patch needs improvement because the docs for when a custom
`aria-describedby` is given definitely needs more details/clarification
about what the user should do to properly customize the attribute for help
text and errors.
--
Ticket URL: <https://code.djangoproject.com/ticket/32819#comment:26>