Referencing HTML elements inside Shadow DOM
by mehm8128 published on
Web Components is the web standard way for creating reusable components like React or Vue components.
While Web Components have matured significantly, it still has some missing pieces to make accessible components easily using Shadow DOM.
Today I'll introduce one of these difficulties and the proposed solution to resolve it. Part of this solution is already available in Chrome Canary.
Problems with referencing HTML elements inside the Shadow DOM
First, let's consider a case where we create a checkbox component using custom elements and Declarative Shadow DOM.
In this example, we want to reference the internal <input> element by referencing id="checkbox" on <fancy-checkbox>. Elements inside the Shadow DOM are encapsulated, that's why we cannot reference the input field directly.
<script>
customElements.define(
"fancy-checkbox",
class FancyCheckbox extends HTMLElement {}
);
</script>
<div>
<label for="checkbox">I agree with the terms and conditions</label>
<fancy-checkbox id="checkbox">
<template shadowrootmode="open">
<input type="checkbox" id="inner-checkbox" />
</template>
</fancy-checkbox>
</div>The problem with this approach is that the <input type="checkbox"> is failing to be associated with the <label>:
- Clicking the label doesn't focus on the checkbox.
- The checkbox doesn't have an accessible name.
This is the missing piece that this article addresses. It is not easy to make Web Components fully accessible using Shadow DOM.
I'll introduce the proposed solutions to address these problems.
Solutions
The proposal divides the work into two phases to simplify the problems.
Phase 1 only deals with targeting a single element in Shadow DOM.
Phase 2 addresses targeting multiple elements.
For the initial release, only Phase 1 will be shipped.
Phase 1
Let's consider an "enclosing element" concept. An "enclosing element" wraps another element and extends it with extra features and HTML elements.
For example, when we create a <fancy-checkbox> component using the Shadow DOM, this becomes the "enclosing element" for <input type="checkbox">. This allows us to encapsulate and make reusable components with custom styling and additional functionality.
To enable referencing the <input type="checkbox"> element inside the Shadow DOM from <label>, Phase 1 introduces the shadowRootReferenceTarget attribute. This attribute specifies which element should be referenced as the target of <label for="checkbox">.
<div>
<label for="checkbox">I agree with the terms and conditions</label>
<fancy-checkbox id="checkbox">
<template shadowrootmode="open" shadowRootReferenceTarget="inner-checkbox">
<input type="checkbox" id="inner-checkbox" />
</template>
</fancy-checkbox>
</div>Now, when <label for="checkbox"> tries to reference <fancy-checkbox id="checkbox">, it automatically references <input id="inner-checkbox" because shadowRootReferenceTarget attribute designates its mapping. Finally, the label can reference the checkbox and provide the accessible name "I agree with the terms and conditions" for it. Users can also focus on it by clicking <label for="checkbox">.
This also enables the use of ARIA attributes, as shown in the following example using aria-labelledby. The same applies to popovertarget, commandfor, and interestfor.
What attributes are in scope is not completely decided. Whether aria-owns should be included will be discussed and all other IDREF attributes are likely to be in scope.
<div>
<input type="text" aria-labelledby="label" />
<fancy-label id="label">
<template shadowrootmode="open" shadowRootReferenceTarget="inner-label">
<label id="inner-label">Type your name.</label>
</template>
</fancy-label>
</div>Phase 1 is available in Chrome Canary with the "Experimental Web Platform features" flag enabled.
Mozilla set their position to "positive" in September, and this feature is proposed as part of Interop 2026. I hope this will become available across all major browsers.
Let's take a look at Phase 2 for multiple elements reference support.
Phase 2
Phase 2 enables referencing multiple elements or other complicated references.
As one of the solution for phase 2, I introduce shadowRootReferenceTargetMap attribute, but other solutions are discussed so it's unclear what solution will be adopted for complicated reference.
Let's consider creating a combobox using aria-controls and aria-activedescendant.
<input
role="combobox"
type="text"
aria-controls="animals"
aria-activedescendant="animals"
/>
<animals-listbox id="animals">
<template
shadowrootmode="open"
shadowRootReferenceTargetMap="aria-controls: listbox,
aria-activedescendant: opt1"
>
<div role="listbox" id="listbox">
<div role="option" id="opt1">Otter</div>
<div role="option" id="opt2">Opossum</div>
<div role="option" id="opt3">Ocelot</div>
</div>
</template>
</animals-listbox>The first part of the value of the shadowRootReferenceTargetMap attribute, aria-controls: listbox, means when aria-controls references <animals-listbox> with the id animals, it should reference an element inside the Shadow DOM with the id listbox. That's <div role="listbox" id="listbox"> here. aria-activedescendant: opt1 works the same way.
This provides the flexibility to reference HTML elements inside the Shadow DOM, but some concerns are under discussion.
Please check out these issues if you're interested in learning more.
- Reference Target Tracking Issue · Issue #1086 · WICG/webcomponents
- Reference Target "phase 2": seeking feedback and use cases · Issue #1111 · WICG/webcomponents
Conclusion
Reference Target for Cross-root ARIA enables us to reference HTML elements inside the Shadow DOM. This makes developing accessible Web Components easier, especially for UI component libraries and design systems. OpenUI is working on The OpenUI Design System, and this feature will be valuable for that project.
I recommend trying this feature in Chrome Canary and providing feedback to the Web Components CG.
About mehm8128
Frontend Engineer at Cybozu in Japan, developing Kintone. Interested in Accessibility, HTML, and UI.
Website: portfolio.hm8128.me
Bluesky: @hm8128.me
Mastodon: @mehm8128