leobalter / cross-root-aria-delegation

Explainer for the Cross-root ARIA delegation proposal
https://leobalter.github.io/cross-root-aria-delegation/
25 stars 3 forks source link

Delegation and attribute reflection #15

Closed mrego closed 1 year ago

mrego commented 2 years ago

Thinking about delegation and how it works for something like aria-labelledby.

Imagine we have a <label> and a custom element <x-input> that has an <input> inside, and we want to set aria-labelledby pointing to the label element outside of the Shadow DOM. Something like this:

<label id="mylabel">The label</label>
<x-input1>
  <template shadowroot="open">
    <input aria-labelledby="mylabel" />
  </template>
</x-input1>

In this case the association between the <input> and the <label> is not set, because the <label> is in a different scope (outside of the Shadow DOM tree).

However with ARIA attribute reflection we could make this work, by using input.ariaLabelledByElements = [mylabel]. And then in that case the association will be set.

I've a codepen with different examples: https://codepen.io/mrego/pen/jOzEjOb?editors=1010 With a screen reader, when you focus the 3 first inputs you get a message like "boo entry" (the boo element is there just to confuse the screen reader, otherwise it might try to get the proper label). But on the 4th, 5th and 6th cases, the ones that use attribute reflection, you get "This is label 4/5/6 entry".

This is how attribute element reflection has been specified on the HTML spec, and also what was agreed a while ago: https://github.com/whatwg/html/issues/6063

One question I have is why this is not allowed when setting directly the attribute, like in the first example here. Could that simply just work? Would that make any sense? If that's a possibility that could solve delegation for element references. Though something else would still be needed for string attributes like aria-label.

CC @joanmarie

mrego commented 2 years ago

Even for string attributes we could try to use attribute reflection.

So imagine the following case:

<x-input id="xinput" aria-label="A label">
  #shadow-root
    <input id="input"/>
</x-input>

And then you could set the aria-label attribute for the inner input using the aria-label on the custom element in JavaScript with input.ariaLabel = xinput.arialabel.

There are some working examples at: https://codepen.io/mrego/pen/mdxVvGm?editors=1010

This is not very nice code, and probably this is missing some use cases; but this doesn't add any new API. Does it makes any sense?

mrego commented 2 years ago

The previous examples were mixing cases of declarative Shadow DOM, and also using the light DOM in some cases, which was kind of confusing (I was mostly playing with different options).

So let's use a new example to clarify things: https://codepen.io/mrego/pen/XWENgZG?editors=1010

In this example we have 2 inputs, one of them where we're setting a DOMString attribute and another for a IDREF one.

Setting the attributes from JavaScript we manage to either set the aria-label attribute for the inner input properly, or setup the relationship between the inner input and the external label.

You can check with Chromium Dev Tools or a screen reader, how the inputs have the proper accessibility information.

nolanlawson commented 2 years ago

@mrego For the aria-label example, it seems to work in Chrome but not Firefox (in terms of the <input> correctly getting the label):

Screen Shot 2022-09-13 at 10 32 34 AM Screen Shot 2022-09-13 at 10 32 24 AM

As for aria-labelledby, your example doesn't seem to correctly apply the label to the <input> in either Chrome or Firefox:

Screen Shot 2022-09-13 at 9 51 31 AM Screen Shot 2022-09-13 at 9 51 36 AM

I quickly tested VoiceOver, and in Chrome it works for both inputs, but for Firefox it only works for the aria-label one.

Probably we would need an exhaustive survey of screenreaders + browsers to see what works and what doesn't, and we may need to look into the details of the accessible name spec to see how it should work.

mrego commented 2 years ago

In Chromium it works for me if I enable experimental web platform features.

Firefox hasn't implemented element reflection yet, and string reflection is behind a flag.

nolanlawson commented 2 years ago

My mistake, I thought your example was just using attributes, not reflected properties. Please disregard my comment.

nolanlawson commented 2 years ago

After discussion with @mrego, I can summarize the issue as the following:

Do we absolutely need cross-root aria delegation if we have aria element reflection?

Rego had an example of using attributeChangedCallback to manually reflect the aria-labelledby from the host to the element inside of the host's shadow root. This works (codepen).

Screen Shot 2022-09-13 at 10 32 34 AM

(This is Chrome Canary with "experimental web platform features" flag on.)

The problem here is that the aria-labelledby element remains on the host, as well as on the <input>. So some screen readers will read this twice, e.g. VoiceOver:

Screen Shot 2022-09-13 at 2 28 41 PM

This was actually raised in a previous discussion (https://github.com/WICG/webcomponents/issues/917#issuecomment-1013561564):

I thought the main advantage was the attribute not applying to the host element when it's delegated.

So this (along with developer ergonomics) is a main benefit we're getting from cross-root aria delegation.

nolanlawson commented 1 year ago

I think we can close this now @mrego @leobalter ^ .

ChromeVox on ChromeOS also seems to do the same thing as VoiceOver+Chrome on macOS.

mrego commented 1 year ago

It looks like there are 2 reasons:

About the duplicated attributes, I wonder how that would work with delegation; I guess we'll need some kind of tweak to make them not a problem, e.g.:

<label id="anotherLabel">Another label</label>
<x-input-idref aria-labelledby="anotherLabel">
  #shadow-root delegatesAriaLabelledBy
    <input autoAriaLabelledBy />
</x-input-idref>

In this case the custom element still has the aria-labelledby attribute, so the screen readers might have problems with that.

So even if we close this, we should take this into account on the final proposal.