Closed Bug 1826194 Opened 2 years ago Closed 1 year ago

HTMLInputElement that reference slot elements with aria-labelledby have no accessibility label

Categories

(Core :: Disability Access APIs, defect)

Firefox 113
defect

Tracking

()

RESOLVED FIXED
116 Branch
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.

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.

Component: Untriaged → Disability Access APIs
Product: Firefox → Core

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.

Blocks: aria
Severity: -- → S3

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.

https://www.w3.org/TR/accname-1.1/

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.

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.

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.

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: nobody → jteh
Blocks: namea11y

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.

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.

Pushed by jteh@mozilla.com: https://hg.mozilla.org/integration/autoland/rev/413022ffe358 part 1: When calculating a text equivalent, don't treat an element with display: contents as invisible. r=morgan https://hg.mozilla.org/integration/autoland/rev/829dc0213048 part 2: When calculating a text equivalent, walk the flat tree instead of only direct children. r=morgan
Status: UNCONFIRMED → RESOLVED
Closed: 1 year ago
Resolution: --- → FIXED
Target Milestone: --- → 116 Branch
Regressions: 1850858
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: