Vue-formulate: Accessibility: Radio/Checkbox groups have semantically improper label associations

Created on 13 Jan 2021  Â·  4Comments  Â·  Source: wearebraid/vue-formulate

Describe 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.

  1. 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" and role="group" attributes to either formulate-input or formulate-input-wrapper. Add the uniqueId as the id of the main label. _This is probably the path of least resistance._

      pug 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 and legend. the div.formulate-input-wrapper would then have to be fieldset.formulate-input-wrapper and the first label would become the legend. This comes with its own styling headaches.

      pug 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

      Both of these approaches work in VoiceOver in Chrome. Uncertain which is overall better supported and since the screen reader landscape is so _glitchy_, I doubt either would be 100%, but it’s important to implement one of them.

  2. The formulate-input-element-decorator labels are linked to the input with for. 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 decorator span or div _inside_ the label 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.)
    pug 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

  1. Go to Box | Vue Formulate
  2. Turn on a screen reader
  3. Hit tab until landing on a checkbox or radio in a group
  4. Note that the context / main label / legend isn’t read

Issue 2: Multiple associated labels for one input

  1. Install aXe accessibility testing tool into your browser of choice
  2. Go to Box | Vue Formulate
  3. Open dev tools and run aXe on the page
  4. 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.

bug report

Most helpful comment

@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 how fieldset operates, and that _may_ not be the preferred way of handling role="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.

All 4 comments

I also just want to chime in and say that vue-formulate is looking pretty dang nifty overall. It’s a similar but superior system to one I've been working on for quite a while, so I'm pretty excited about it. I'd been writing my own to achieve better accessibility standards than I've found in other frameworks. I see a _lot_ of promise here though, and especially like that I can write custom components where needed. Good job to the team. Kudos. 🎉

@aminimalanimal I appreciate the heads up on these issues. I think I speak for all the contributors of Vue Formulate when I say we're keenly interested in ensuring accessibility is a key priority. Especially considering making improvements in this one library can improve forms on hundreds or thousands of other sites.

Your first issue can be addressed immediately via the role="group" and aria-labelledby="uniqueId" as you suggest. The release/2.5.0 branch is in deep development at the moment. I'll try to get these included.

The second issue is definitely more challenging since it requires a breaking change to the DOM that is already being output. However, the multiple label issue has come up before actually — specifically #313 which proposed to allow the disabling of the second checkbox all together. This is implemented in the unpublished, but public, release/2.5.0 branch. For the time being this is probably as far as the 2.x series can take the improvement.

However, as we close out 2.5 our attention is turning to a full re-write 3.x which can include breaking changes. Among a number of other improvements, one thing im keen to see improved in 3.x is overall accessibility. We've had some conversations with experts in the field and any additional assistance you want to provide on the 3.x development will be greatly appreciated.

@aminimalanimal Ive just pushed a new commit on the release/2.5.0 branch that includes the aria-labledby and role="group" attributes. I'd love if you could take a peek at it and render an opinion on it 👍

If you clone that branch, npm install, and run npm run dev you should be able to see a "specimen sheet" of all the inputs including some checkbox samples.

@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 how fieldset operates, and that _may_ not be the preferred way of handling role="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.

Was this page helpful?
0 / 5 - 0 ratings