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.
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”).: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
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
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.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
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.
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.
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="groupto 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 howfieldsetoperates, 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.