diff --git a/exportid-explainer.md b/exportid-explainer.md new file mode 100644 index 0000000..ca6fffe --- /dev/null +++ b/exportid-explainer.md @@ -0,0 +1,549 @@ +# Exporting IDs from shadow roots for cross-root ARIA + +Authors: [Ben Howell](https://github.com/behowell), [Alice Boxhall](https://github.com/alice) + +## Introduction + +The Shadow DOM provides a powerful way to encapsulate web components and keep their implementation details separate from other code on the page. However, this presents a problem for accessibility, which needs to establish semantic relationships between elements on the page. There is currently no way to refer to an element inside another shadow tree from an attribute like `aria-labelledby`. Referring to elements across shadow root boundaries is called "cross-root ARIA", although it affects non-ARIA properties like the label's `for` attribute as well. + +For more detailed background on the problem and other proposals to solve it, see Alice Boxhall's article [How Shadow DOM and accessibility are in conflict](https://alice.pages.igalia.com/blog/how-shadow-dom-and-accessibility-are-in-conflict/). + +As laid out in the article, there are separate but related problems to solve: + +* [Referring into Shadow DOM](https://alice.pages.igalia.com/blog/how-shadow-dom-and-accessibility-are-in-conflict/#referring-into-shadow-dom): An element in the light tree needs to create a relationship like `aria-activedescendant` to an element inside a shadow tree. +* [Referring from Shadow DOM outwards](https://alice.pages.igalia.com/blog/how-shadow-dom-and-accessibility-are-in-conflict/#referring-from-shadow-dom-outwards): An element inside a shadow tree needs to create a relationship like `aria-labelledby` with an element in the light tree. +* There is also the combined case, where an element in one shadow tree needs to refer to an element in a sibling shadow tree (or any relationship that is not a direct ancestor/descendant relationship). A complete solution should work in this case as well. + * An example of when this is needed is described by Nolan Lawson: [ARIA element reflection across non-descendant/ancestor shadow roots](https://github.com/WICG/aom/issues/192). + +The cross-root ARIA problem has been discussed for several years, and there have been many proposed solutions. Existing proposals are described below, in the **Alternative Solutions** sections. This proposal draws on the ideas from many of the other proposals. + +## Proposal: Exported IDs + +The `exportid` attribute is a way to allow an element inside a shadow tree to be referred to via an ID reference attribute like `aria-labelledby` or `for`, while preserving shadow DOM encapsulation. + +**Goals** + +* Create a mechanism for elements to refer to each other across shadow root boundaries through ID reference attributes like `aria-labelledby` or `for`. +* The solution should work the same for both closed and open shadow roots. +* Shadow DOM encapsulation should be preserved: Exporting an ID doesn't directly allow access to the underlying element. It is still required to have access to the shadow root to get the element. +* The solution should allow creating references across multiple nested shadow roots, +and across "sibling" shadow roots +(i.e. from an element within one shadow root, +to an element within a shadow root attached to an element in the light tree of the first shadow root). +* The solution should be serializable, i.e. expressible in HTML only. + +**Non-Goals** + +* Exported IDs are not available for CSS styling. That is the role of shadow parts. +* This proposal does not help with ARIA attributes that aren't ID references, such as `aria-label`. + + +## Referring to elements in a shadow tree + +A new boolean attribute `exportid` specifies that an element is able to be referenced from outside of its shadow tree in attributes that support ID references. + +Elements outside of the shadow tree can refer to an exported ID with the syntax `"thehost::id(thechild)"`. In this example, `"thehost"` is the ID of the element that contains the shadow root, and `"thechild"` is the ID of an element inside the shadow tree that also has `exportid`. See the example below. + +Exported ID references can be used in any attribute that refers to an element by ID, such as `for` or `aria-labelledby`. + +#### Example 1: Referring into the shadow tree using exportid + +```html + + + #shadowRoot + | + +``` + +> Note: See the [Requirements for `exportid`](#requirements-for-exportid) section for details on `id` values used with `exportid`. + +### Forwarding exported IDs + +For the relatively uncommon case of nested shadow roots, IDs can be "forwarded" from the outer shadow root using the `forwardids` attribute, +analogous to how CSS `part`s may be exported via [`exportparts`](https://drafts.csswg.org/css-shadow-parts/#exportparts-attr). + +> Note: It is not allowed to chain together multiple `::id()` values to refer into a nested shadow tree. For example, the following is not valid, and would not match anything: `for="x-combobox::id(x-input)::id(the-input)"`. This is to avoid exposing more structure than a component author may desire, and follows the same restrictions as the CSS `::part()` selector. + +#### Example 2: Referring through multiple layers of shadow trees + +```html + + + #shadowRoot + | + | #shadowRoot + | | + | + | + +``` + +Multiple IDs can be forwarded, separated by a comma; e.g. `forwardids="id1, id2"`. + +Forwarded IDs can also optionally be renamed: `forwardids="name1, inner-name2: outer-name2"`. + +#### Example 3: Renaming an exported ID to avoid a collision + +```html + + + #shadowRoot + | + | + | #shadowRoot + | | + | + | + | + | #shadowRoot + | | + | + #/shadowroot + + + +``` + +## Referring out of the shadow tree with `useids` + +A much more common need is to refer from a shadow tree out into the light tree. +This is already possible using [reflected `Element` IDL attributes](https://html.spec.whatwg.org/#reflecting-content-attributes-in-idl-attributes:element), e.g. `shadowInput.ariaDescribedByElement = lightDiv;`. +However, this can't be serialized, and doesn't allow referring into "sibling" shadow roots. + +The `useids` attribute "imports" IDs from the light tree into a shadow tree, via a mapping from `inner-id: outer-idref`. +The `inner-id` names are determined by the web component author, much like a `slot` name; +the `outer-idref` names are provided by the user of the web component to refer to some specific element outside the component. + +Within the component's shadow tree, imported IDs are referenced using the `:host::id()` syntax, using the special 'ID' `:host` to refer to IDs specified by `useid` on the host element. This syntax is analagous to the `:host::part()` CSS selector that can be used to select parts in the local tree. + +Note: There is an open question below discussing an [alternative syntax for useids](#open-question-useids). + +#### Example 4: Importing IDs with `useids` + +This example shows how to import an ID called `"hint"` into the `` component, and reference it using the `":host::id()"` syntax. + +```html + + #shadowRoot + | + +Your name can be in any language. +``` + + + +### Referring across sibling shadow trees + +When using `useids`, `outer-idref` values may be straightforward IDs, +but also may use the `::id()` syntax to refer into sibling shadow roots. + +#### Example 5: `