WICG / webcomponents

Web Components specifications
Other
4.39k stars 375 forks source link

RfC: API design for aria delegation mechanism #917

Open dfreedm opened 3 years ago

dfreedm commented 3 years ago

Synopsis

The AOM group has been discussing a "delegation" API to allow for ARIA attributes and properties set on a webcomponent to be forwarded to elements inside of its shadowroot as a simpler solution to the issues raised in WICG/aom#169. This mechanism will allow users to apply standard best practices for ARIA, and should solve a large margin of accessibility use cases. This API is most suited for one-to-one delegation, but should also work for one-to-many setups. There is no mechanism for directly relating two elements in different shadowroots together, but this will still be possible manually with the element reflection API.

The AOM group considered two very similar API proposals, ElementInternals::delegate and ShadowRoot::delegate. Both have the same syntax, but the group did not agree strongly on whether ElementInternals or ShadowRoot was the "better" fit.

Example

The my-input element delegates the aria-label and aria-describedby attributes, and ariaLabel and ariaDescribedByElements properties to an internal input element.

Users of my-input can use either the attributes or properties as expected, and the internal input element will announce instead of my-input.

<span id="description">Description</span>
<!-- announces nothing for the screen reader -->
<my-input aria-label="Label" aria-describedby="description">
  <!-- shadow root -->
  <!-- announces with label "Label" and description "Description" -->
  <input>
  <!-- /shadow root -->
</my-input>
class MyInput extends HTMLElement {
  constructor() {
    super();
    const input = document.createElement('input');
    this.shadowRoot.appendChild(input);
    // Option 1
    this.shadowRoot.delegate({ariaLabel: input, ariaDescribedByElements: input});
    // Option 2
   const internals = this.attachInternals();
   internals.delegate({ariaLabel: input, ariaDescribedByElements: input});
  }
}
dfreedm commented 3 years ago

Example Glitch for ElementInternals approach Example Glitch for ShadowRoot approach

rniwa commented 3 years ago

During F2F today, we discussed an alternative where we'd add a boolean something like delegatesLabel like we have for delegatesFocus and introduce a new content attribute like autolabel (we can bikeshed this). This has an advantage that it works with declarative Shadow DOM as well, and it is consistent with delegatesFocus. It also works better with React-like DOM updating model where you may not necessarily have access to a DOM node right where you're creating / declaring it.

dfreedm commented 3 years ago

Example with ShadowRootInit flags and content attributes

annevk commented 3 years ago

FWIW, delegatesLabel is https://github.com/WICG/webcomponents/issues/916.

joanmarie commented 3 years ago

Is the plan for there to be a whole series of per-property delegatesFoo? (e.g. delegatesHasPopup, delegatesExpanded, delegatesSetSize, delegatesDescription, etc.)?

leobalter commented 2 years ago

I'm assigning this work to myself as discussed today at the AOM meeting. I don't have write permissions to do this formally in this github org, but this comment might be good enough for now.

The general understanding is that we have consensus for this and the next step is drafting the specs.

Following this I should work on #916.

Thanks!

domenic commented 2 years ago

To ensure I understand, this proposal gives no new capabilities; it just makes it easier to coordinate instead of manually synchronizing someDescendant.ariaLabel with elementInternals.ariaLabel?

leobalter commented 2 years ago

My understanding is this proposal allows usage of web components to have aria attributes in their top layers that are then delegated to their encapsulated shadow dom within the discretion of their constructor. For this it seems like a new capability but I might be corrected.

This is useful for resolving aria attributions top down to web components, but the main goal to achieve is the full cross-root aria challenge. For that, #916 might be the next stone.

domenic commented 2 years ago

I believe that is already possible using code such as the following (referring to the example in the OP):

class MyInput extends HTMLElement {
  static observedAttributes = ["aria-label"];
  #input;

  constructor() {
    super();
    this.#input = document.createElement('input');
    this.shadowRoot.appendChild(this.#input);

    this.#input.ariaLabel = this.ariaLabel;
  }

  attributeChangedCallback(name, newValue, oldValue) {
    if (name === "aria-label") {
      this.#input.ariaLabel = this.ariaLabel;
    }
  }
}

Can you spot anything that this proposal would enable that code like the above would not? It would be really helpful to understand the scope of the proposal to get a clear answer.

justinfagnani commented 2 years ago

I thought the main advantage was the attribute not applying to the host element when it's delegated. For instance when delegating aria-label you want readers to know that it applies to the input, not both the input and the host. My understanding is also that current workarounds include copying the attribute over and removing it from the host, but that introduces other problems.

Is that correct @dfreedm ?

leobalter commented 2 years ago

I'm working on a document on how this and 916 applies to my current use cases. I might be missing a point with this current solution vs what @domenic pointed out. I need time to investigate this and I hope to come in with a good conclusion on the following steps next week.

asyncLiz commented 2 years ago

I thought the main advantage was the attribute not applying to the host element when it's delegated. For instance when delegating aria-label you want readers to know that it applies to the input, not both the input and the host. My understanding is also that current workarounds include copying the attribute over and removing it from the host, but that introduces other problems.

Is that correct @dfreedm ?

That is correct!

WickyNilliams commented 2 years ago

@domenic your example works with aria-label, since its value is the literal label text, but wouldn't work with id-based relationships like aria-labelledby or aria-describedby? This proposal seems to open up support for the latter, judging by the example in OP

Westbrook commented 3 months ago

Feels like some of this is closed by https://github.com/WICG/webcomponents/blob/gh-pages/proposals/reference-target-explainer.md but there is deeper nuance that should continue to be discussed in the form of: https://github.com/WICG/webcomponents/issues/1068