headlessui
headlessui copied to clipboard
Combobox uses `aria-labelledby` instead of `for` attribute on label
What package within Headless UI are you using? @headlessui/react
What version of that package are you using? v1.5.0
What browser are you using? Any
Reproduction URL https://stackblitz.com/edit/nextjs-rfh5ei?file=pages%2Findex.js
Describe your issue
<Combobox.Label> should auto-generate a for attribute that matches the id on the <Combobox.Input>. Instead, <Combobox.Input> uses aria-labelledby, which points to the id of the label.
Is there a reason to use this approach instead of the native for attribute?
Here's an example of the rendered markup:
<label id="headlessui-combobox-label-11">Search for people</label>
<input id="headlessui-combobox-input-12" role="combobox" type="text" aria-expanded="false" aria-labelledby="headlessui-combobox-label-11">
And the expected markup:
<label for="headlessui-combobox-input-12">Search for people</label>
<input id="headlessui-combobox-input-12" role="combobox" type="text" aria-expanded="false">
Hey! Thank you for your question! Much appreciated! 🙏
The short answer is "copy & paste" issue, the longer answer is that we have to fix it but it isn't super straightforward due to the as prop. If people use the default components then it is a non-issues. But the moment somebody does <Combobox.Label as="p"/> for whatever reason, then we have to fallback to the aria-labelledby strategy because a for on a p won't work as expected compared to the label.
While I agree that the native label is a better use case for this. Is this causing any issues for you at the moment? In my testing it results in the same spoken text when using VoiceOver: https://76dytw.csb.app/
The only downside in that example is that you can't click the "label" in the case when using the aria-labelledby, but in Headless UI this is a non-issue because we have a click listener for that since we also use that code where we can't immediately attach it to an input.
@RobinMalfait Thanks for the background on this!
That makes sense with the as prop in play. Is it feasible to potentially use a native for attribute as the default <Combobx.Label> rendering as a <label>, and fallback to the aria-labelledby when an as value is provided?
In my testing with VoiceOver and NVDA, the input's accessible name is announced as expected, but various linting tools or validators might through a warning or error due to a lack of a <label> in the DOM, which might be creating noise for users.
The short answer is "copy & paste" issue, the longer answer is that we have to fix it but it isn't super straightforward due to the
asprop. If people use the default components then it is a non-issues. But the moment somebody does<Combobox.Label as="p"/>for whatever reason, then we have to fallback to the aria-labelledby strategy because aforon apwon't work as expected compared to thelabel.
What reason can there be to use a p (or any other non-label element) as a label for an input? Given that there are no styling issues for labels, I know of no use case as to why any other element should be needed / allowed.
One of the basic tenets of accessibility is to use semantic elements. The first rule of ARIA is to only use it when the functionality isn't available natively. As i said above: i see no use case that would necessitate the current complex solution over the very simple native HTML solution.
Allowing people to use another element will result in untested situations with unsuspected bugs & that will get people flagged down by auditors. It also means that if JS fails for whatever reason (and that happens more then most devs think): the accessibility is gone, even with SSR.
As an aside: the component also allows for the lack of a label. I noticed that when looking at the example combobox' code (https://headlessui.com/react/combobox). That is also an accessibility violation.