HTMLInputElement that reference slot elements with aria-labelledby have no accessibility label
Categories
(Core :: Disability Access APIs, defect)
Tracking
()
Tracking | Status | |
---|---|---|
firefox116 | --- | fixed |
People
(Reporter: clshortfuse, Assigned: Jamie)
References
(Blocks 2 open bugs, Regressed 1 open bug)
Details
Attachments
(2 files)
User Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36
Steps to reproduce:
Inside a Web Component, an HTMLInputElement that uses [aria-labelledby] to reference an HTMLSlotELement, will not have any label, regardless of contents of slot.
See following tree:
<x-button>
#shadow-root
<input type=button aria-labelledby=slot>
<slot id=slot></slot>
</x-button>
A more exhaustive list of failures and reproducibles is available at: https://codepen.io/shortfuse/pen/PodMYBo
Actual results:
Accessibility Tree / Screen Readers report HTMLInputElement to be blank. Forcing the HTMLSlotElement to have a CSS display property other than contents
will apply the label.
Expected results:
HTMLInputElement should take the text content of the HTMLSlotElement as its label.
Also, if the <slot> element has [aria-hidden=true], the HTMLInputElement should also be able to apply its textContent as the control label, while hiding the actualy HTMLSlotElement from the accessibility tree.
Comment 1•2 years ago
|
||
The Bugbug bot thinks this bug should belong to the 'Core::Disability Access APIs' component, and is moving the bug to that component. Please correct in case you think the bot is wrong.
Assignee | ||
Comment 2•2 years ago
|
||
This does seem like a useful thing to support, but I wonder whether the ARIA specs actually support this, since slots themselves aren't rendered into the a11y tree. A slot by definition is considered to be replaced by its contents. If not, this might need some spec work.
Reporter | ||
Comment 3•2 years ago
|
||
Thanks for taking time to take a look, (and I see you're tagged as away).
I don't think it's a matter of it being within spec. I think it's a bug related to display:contents
. Here are a couple other trees when using <x-button>foo</x-button>
and their a11y label :
<x-button>
#shadow-root
<input type=button aria-labelledby=slot>
<slot id=slot style="display:inline-block"></slot>
</x-button>
Result: pushbutton: "foo"
<x-button>
#shadow-root
<input type=button aria-labelledby=wrap>
<div id=wrap><slot></slot></div>
</x-button>
Result: pushbutton: "foo"
<x-button>
#shadow-root
<input type=button aria-labelledby=wrap>
<div id=wrap style="display:contents"><slot></slot></div>
</x-button>
Result: pushbutton: ""
The fact the label changes based on display
of an element is likely a bug. Even if an element has display:none
, it should be able to referenced for aria-labelledby
. This also applies for elements with aria-hidden=true
. I used the original example because it's most minimal reproduction. The ideal tree for ShadowDOM would be:
<x-button>
#shadow-root
<input type=button aria-labelledby=slot>
<slot id=slot aria-hidden=true></slot>
</x-button>
This would hide the slot from the accessibility tree and since it would only serve as a label for the button. It avoids the minor possibility of some screen-readers reading the button with it's label and then repeating the slot contents.
Here is an article explaining how element visibility and aria-hidden
should not affect aria-labelledby
: https://www.tpgi.com/short-note-on-aria-labelledby-and-aria-describedby/
The cited part of the spec is:
By default, assistive technologies do not relay hidden information, but an author can explicitly override that and include hidden text as part of the accessible name or accessible description by using aria-labelledby or aria-describedby.
Assignee | ||
Comment 4•2 years ago
|
||
Thanks for the considered reply.
If it were display: contents and not specific to slot, this would also fail, but it doesn't:
data:text/html,<button aria-labelledby="label"></button><div id="label" style="display: contents;">hi
slots are somewhat special in that they are considered replaced by their contents. I'd say display: contents is definitely part of this, but not the whole.
Reporter | ||
Comment 5•2 years ago
|
||
Yep. There's something going on with ShadowDOM / Slot. The codepen in the original comment has 32 different setups (16 ShadowDOM / 16 LightDOM). With light DOM, regardless of any configuration I throw at it, related to display:contents
, it all parses fine. (With a minor exception that FireFox does not use the layout to see words should not concatenated)
But with shadow DOM, even <label>
based parsing gets wonky when using <slot>.
<x-button>
#shadow-root
<label>
<input type=button>
<slot></slot>
</label>
</x-button>
Result: label: "foo"
+ pushbutton: "foo"
<x-button>
#shadow-root
<label style="display:contents">
<input type=button>
<slot></slot>
</label>
</x-button>
Result: label: "foo"
+ pushbutton: ""
The near exact Light DOM setup works fine (replace <slot>
with <div style="display:contents">
). The codepen really gets into detail, but label-wrapping is better suited for Light DOM to avoid IDs. It shouldn't needed in Shadow DOM if we can just refer to an element. So, I'm not too interested in targeting <label>
in shadow DOM issues.
Reporter | ||
Comment 6•1 year ago
|
||
Sorry to be persistent on this, but are there any active plans to work on this? Safari team has fixed the bug which leaves it as a Firefox exclusive issue. https://bugs.webkit.org/show_bug.cgi?id=254934
I had a workaround in place, but I realized that's it doesn't work consistently unless I also put mutation observer to watch to characterData
changes:
<x-button>
#shadow-root
<input type=button aria-labelledby=slot aria-label="">
<slot id=slot onslotchange="{ this.previousElementSibling.setAttribute('aria-label', this.textContent) }"></slot>
</x-button>
That's because slotchange
event on HTMLSlotElement
won't fire if a text node has it's .data
changed, which is pretty standard practice for most render frameworks I've seen (they don't replace Text nodes, they just update them).
The workaround is rather verbose and JS dependent, and I use <input>
elements heavily for web components.
Assignee | ||
Comment 7•1 year ago
|
||
Implementation note: distilled test case:
data:text/html,<div id="host">hi</div><script> const shadow = host.attachShadow({ mode: "open" }); shadow.innerHTML = '<input type="button" aria-labelledby="slot"><slot id="slot"></slot>'; </script>
The slot does get an Accessible due to the aria-labelledby reference. If you remove the slot's id, it doesn't get an Accessible because there's no ARIA property referencing the slot, which is expected. So, if we're correctly creating an Accessible, why aren't we calculating the label correctly?
Assignee | ||
Comment 8•1 year ago
|
||
A slot element has display: contents by default.
Previously, when computing a text equivalent, we would ignore the accessibility subtree if there was no frame.
This made sense back when no frame always meant display: none, but now, it could also mean display: contents.
To fix this, just look at whether the DOM node has an Accessible; don't check visibility at all.
If something really is invisible (including visibility: invisible), it shouldn't be in the a11y tree anyway.
Note that even without this fix, we should have been able to get the text equivalent for a slot by falling back to walking the DOM tree instead of the a11y tree.
Unfortunately, that is also broken... but the next patch will fix it.
Assignee | ||
Comment 9•1 year ago
|
||
Previously, when calculating a text equivalent by walking the DOM tree, we only walked direct children of a DOM node.
However, a slot element is a placeholder for content which comes from outside of a shadow root.
In order to include this content, we need to walk the flat tree instead.
Comment 10•1 year ago
|
||
Comment 11•1 year ago
|
||
bugherder |
https://hg.mozilla.org/mozilla-central/rev/413022ffe358
https://hg.mozilla.org/mozilla-central/rev/829dc0213048
Description
•