Accessibility: Radio/Checkbox groups have semantically improper label associations
See original GitHub issueDescribe the bug
The output for a radio/checkbox group comes out in this structure:
div.formulate-input(data-classification="group" data-type="checkbox")
div.formulate-input-wrapper
label.formulate-input-label.formulate-input-label--before(for="formulate--guide-inputs-types-box--2") This is a label for all the options
div.formulate-input-element.formulate-input-element--group.formulate-input-group
div.formulate-input-group-item.formulate-input(data-classification="box" data-type="checkbox")
div.formulate-input-wrapper
div.formulate-input-element.formulate-input-element--checkbox(data-type="checkbox")
input#formulate--guide-inputs-types-box--2_first(type="checkbox" value="first")
label.formulate-input-element-decorator(for="formulate--guide-inputs-types-box--2_first")
label.formulate-input-label.formulate-input-label--after(for="formulate--guide-inputs-types-box--2_first") First
div.formulate-input-group-item.formulate-input(data-classification="box" data-type="checkbox")
div.formulate-input-wrapper
div.formulate-input-element.formulate-input-element--checkbox(data-type="checkbox")
input#formulate--guide-inputs-types-box--2_second(type="checkbox" value="second")
label.formulate-input-element-decorator(for="formulate--guide-inputs-types-box--2_second")
label.formulate-input-label.formulate-input-label--after(for="formulate--guide-inputs-types-box--2_second") Second
There are a couple of issues with this output from an accessibility perspective.
- The first
label
is not associated with anything. A screen reader user that lands on one of these checkboxes/radios will not necessarily hear the context of the entire question. This is especially problematic for forms that might ask several questions with the same answers (such as “agree, neutral, disagree”). There are two options to properly ensure that the context of the question is linked to the radios/checkboxes.- WAI-ARIA: Add
:aria-labelledby="uniqueId"
androle="group"
attributes to eitherformulate-input
orformulate-input-wrapper
. Add theuniqueId
as theid
of the main label. This is probably the path of least resistance.div.formulate-input(role="group" aria-labelledby="unique-id" data-classification="group" data-type="checkbox") div.formulate-input-wrapper label.formulate-input-label.formulate-input-label--before(id="unique-id") This is a label for all the options
- Semantic HTML: Use
fieldset
andlegend
. thediv.formulate-input-wrapper
would then have to befieldset.formulate-input-wrapper
and the firstlabel
would become thelegend
. This comes with its own styling headaches.div.formulate-input(data-classification="group" data-type="checkbox") fieldset.formulate-input-wrapper legend.formulate-input-label.formulate-input-label--before(for="formulate--guide-inputs-types-box--2") This is a label for all the options
- WAI-ARIA: Add
- The
formulate-input-element-decorator
labels are linked to theinput
withfor
. I recognize that this is being done to ensure that clicking on the decorator label toggles the associated input (and that the decorator label is going to be used for styling purposes), but it poses a larger issue for screen readers because most screen readers don’t support multiple labels. It is likely then, that a screen reader would choose the first label to associate with the input, which in this case would provide no valuable information to the user. The simplest solution to this would likely be to place the decoratorspan
ordiv
inside thelabel
that contains the radio button/checkbox’s label content. (These days it’s becoming more and more possible to use pseudo-elements to style these elements, so the decorators may not even be strictly necessary.)div.formulate-input-group-item.formulate-input(data-classification="box" data-type="checkbox") div.formulate-input-wrapper div.formulate-input-element.formulate-input-element--checkbox(data-type="checkbox") input#formulate--guide-inputs-types-box--2_first(type="checkbox" value="first") label.formulate-input-label.formulate-input-label--after(for="formulate--guide-inputs-types-box--2_first") span.formulate-input-element-decorator | First
To Reproduce
Issue 1: Unassociated legend
- Go to Box | Vue Formulate
- Turn on a screen reader
- Hit tab until landing on a checkbox or radio in a group
- Note that the context / main label / legend isn’t read
Issue 2: Multiple associated labels for one input
- Install aXe accessibility testing tool into your browser of choice
- Go to Box | Vue Formulate
- Open dev tools and run aXe on the page
- Several instances of “Form field should not have multiple label elements” appear
Expected behavior
Screen reader users should be able to jump to radio/checkbox groups and be able to hear the context of the question being asked of them, as well as the label associated with the radio/checkbox input. Deque’s aXe should not throw warnings/errors.
Issue Analytics
- State:
- Created 3 years ago
- Comments:9 (3 by maintainers)
Top Results From Across the Web
Option to disable decorator in InputBox · Issue #313 - GitHub
I'd like the option to disable the rendering of the decorator in the InputBox. Some accessibility checking software flag it as an empty...
Read more >Grouping Controls | Web Accessibility Initiative (WAI) - W3C
Grouping related form controls makes forms more understandable for all users, ... As the labels in both groups have the same text, the...
Read more >Navigation in Lists: To Be or Not To Be - CSS-Tricks
Dustin's article talks about semantics, accessibility, and screen readers, but has no research and doesn't explain why the listless navigation ...
Read more >QTI v3 Best Practices and Implementation Guide - 1EdTech
The following participating organizations have made explicit license commitments to ... QTI 3 brings together QTI 2.x and Accessible Portable Item Protocol® ...
Read more >#32338 (Accessibility issues with Django forms RadioSelect ...
Note how the first field has the wrong label due to the markup. ... Lack of a fieldset means no semantic grouping. The...
Read more >
Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free
Top Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
@justin-schroeder Just checked out
release/2.5.0
. This is a massive improvement. Thanks so much.This implementation differs from my description above, but it’s potentially better. This implementation assigns
role="group
to the container directly around the radios/checkboxes whereas I described it being on the outer container, but my inclination of where to place it was based on howfieldset
operates, and that may not be the preferred way of handlingrole="group"
. In any case, the differences in how a screen reader interprets the two are subtle, and I’m honestly not certain which way would be preferred. I’m quite happy with this approach.I ran the tests. Results here. Not tested: JAWS, Android. https://codepen.io/aminimalanimal/pen/NqbXKX
Overall, of modern browsers, iOS is the one that messes up what otherwise is pretty darn consistent support for both. iOS does not seem to support
role="group"
. I may file a bug for that.If you’re supporting IE 11 (and hopefully you aren’t),
role="group"
is actually preferable.I have definitely run into complications with
fieldset
styling. I know there are workarounds to it, but that’s not the point. If you’re creating a system where the developer isn’t going to have a lot of control over the HTML output, you need to be aware of the limitations your system imposes upon them, andfieldset
is a special case.