Skip to content

Commit a4c4aac

Browse files
committed
docs: update a11y skill cross-root aria instructions
1 parent 1d61732 commit a4c4aac

1 file changed

Lines changed: 139 additions & 7 deletions

File tree

.claude/skills/review-a11y/SKILL.md

Lines changed: 139 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -147,13 +147,23 @@ For each composite element:
147147
(selected option, active tab, etc.)
148148
6. Close/collapse, snapshot again -- verify clean return to closed state
149149

150-
**Cross-root relationships**: `aria-controls`, `aria-describedby`,
151-
`aria-labelledby` cannot reference IDs across shadow boundaries
152-
declaratively, they must (as of Spring 2026) use ARIA IDL attrs like
153-
`element.ariaDescribedByElements`. Verify these relationships work via
154-
ElementInternals ARIA IDL properties or other workarounds.
155-
Flag any broken cross-root references. Ensure that all ax tree states in demo
156-
are suitably covered by a11ySnapshot tests in element's test files.
150+
**Cross-root ARIA relationships**: See the reference section below for
151+
the full technical landscape. Key points for reviewers:
152+
153+
- `aria-controls`, `aria-describedby`, `aria-labelledby` **cannot**
154+
reference IDs across shadow boundaries declaratively.
155+
- ARIA IDL properties (`ariaDescribedByElements`, etc.) are the
156+
intended replacement but have cross-root limitations. See
157+
"Cross-Root ARIA Element Reflection" below for which directions
158+
work and which silently fail.
159+
- Verify relationships in the **ax tree**, not by inspecting DOM
160+
attributes. A relationship that appears set in JS may produce
161+
nothing in the computed tree.
162+
- Flag any broken cross-root references and document workarounds
163+
(live-region announcer, `aria-label` on trigger, Reference Target
164+
when it ships).
165+
- Ensure all ax tree states in demos are covered by a11ySnapshot
166+
tests in the element's test files.
157167

158168
#### Cross-browser differences to flag
159169

@@ -199,3 +209,125 @@ In "Critical Issues", all items should be linked to its WCAG success criterion..
199209
- Leave "why comments" on non-obvious ARIA decisions
200210
- Both browsers must produce functionally equivalent ax trees
201211
- Test every state, not just default
212+
213+
## Cross-Root ARIA Element Reflection
214+
215+
Reference material for reviewing cross-shadow-boundary ARIA
216+
relationships. Last updated June 2026.
217+
218+
### The Problem
219+
220+
Declarative ARIA ID references (`aria-describedby="some-id"`) only
221+
resolve within a single DOM tree. Shadow DOM boundaries prevent
222+
cross-root ID resolution. This affects any element where a trigger in
223+
light DOM needs an ARIA relationship to content in shadow DOM (or
224+
vice versa).
225+
226+
### ARIA IDL Element Reflection
227+
228+
The ARIA IDL properties (`ariaDescribedByElements`,
229+
`ariaLabelledByElements`, `ariaControlsElements`, etc.) accept Element
230+
references instead of ID strings, bypassing the ID-resolution problem.
231+
However, cross-root references are validated by a **shadow-including
232+
ancestors** algorithm that restricts which directions work.
233+
234+
#### Which directions work
235+
236+
| Direction | Example | Status |
237+
|---|---|---|
238+
| Same tree | Light DOM element → light DOM element | Works |
239+
| Shadow-to-light (child→parent) | Shadow DOM element → light DOM ancestor | Works |
240+
| Light-to-own-shadow (parent→child) | Light DOM trigger → shadow DOM content in same host | Permitted by spec algorithm, but **not WPT-tested** and **unreliable in practice** |
241+
| Sibling shadow roots | Element in shadow root A → element in shadow root B | Does NOT work |
242+
| Arbitrary cross-root | Unrelated shadow boundaries | Does NOT work |
243+
244+
The parent-to-child case (light DOM trigger referencing its host's
245+
shadow DOM content) is technically valid per the shadow-including
246+
ancestors rule, but:
247+
248+
- WPT tests at `custom-elements/element-internals-aria-element-reflection.html`
249+
only cover same-tree and negative (invalid cross-tree) cases
250+
- WICG/webcomponents#974 explored this direction with no clear resolution
251+
- No browser has confirmed reliable support
252+
- Treat as **progressive enhancement**: set the IDL property, but don't
253+
rely on it for accessible content delivery
254+
255+
#### What to do instead
256+
257+
When a cross-root ARIA relationship silently fails, use one of these
258+
workarounds depending on the element's semantics:
259+
260+
| Relationship | Workaround |
261+
|---|---|
262+
| `describedby` (supplementary text) | Static `role="status"` live-region announcer in light DOM (see ADVICE.md "Use a static live-region announcer") |
263+
| `labelledby` (accessible name) | Set `aria-label` directly on the trigger element |
264+
| `controls` (widget relationship) | Verify the ax tree reflects the relationship via native semantics (`<details>`, `<dialog>`) or ElementInternals; if not, document the gap |
265+
| `none` (opt-out) | Provide a `silent` or similar boolean attribute to suppress ARIA behavior |
266+
267+
### Reference Target Proposal
268+
269+
The **Reference Target** proposal (WICG/webcomponents#1086) is the
270+
long-term solution for cross-root ARIA. It enables external elements to
271+
reference a shadow host by ID, with the shadow root designating which
272+
internal element receives the reference via `shadowRootReferenceTarget`.
273+
274+
#### Status (June 2026)
275+
276+
| Browser | Status |
277+
|---|---|
278+
| Chrome | Canary behind `--enable-experimental-web-platform-features` flag. Intent to Experiment filed. Not in stable. |
279+
| Firefox | Positive standards position. Igalia prototyping (NLNet funded). Bug 1981341. Not shipping. |
280+
| Safari/WebKit | Igalia prototype exists. No official standards position signal. |
281+
| Spec | WHATWG HTML #10707, Stage 3 ("Committed"). Pull requests DOM #1353, HTML #10995 in progress. Not yet merged into HTML Living Standard. |
282+
| Interop 2026 | Submitted as focus area proposal (web-platform-tests/interop#1011). |
283+
284+
#### What Reference Target solves
285+
286+
- **Outside → host → internal target** references (e.g., a label
287+
element outside a shadow root referencing an input inside it via
288+
the host)
289+
- Recursive resolution through nested shadow roots
290+
291+
#### What Reference Target does NOT solve
292+
293+
- Arbitrary sibling-to-sibling cross-root references
294+
- Direct parent-to-specific-child references bypassing the host
295+
- Attribute forwarding from host to internal elements
296+
- The tooltip case where the *trigger* is in light DOM and the
297+
*content* is in the host's own shadow DOM (this is the reverse
298+
direction)
299+
300+
### Reviewing elements with cross-root ARIA
301+
302+
When auditing an element with cross-root ARIA relationships:
303+
304+
1. **Identify all ARIA relationships** the element establishes
305+
(describedby, labelledby, controls, owns, etc.)
306+
2. **Map each relationship's direction**: which element sets the
307+
property, which element is referenced, and which shadow roots are
308+
involved
309+
3. **Check the ax tree in both browsers** to see if the relationship
310+
actually resolves (don't trust DOM inspection)
311+
4. **Verify the fallback mechanism** works:
312+
- If using an announcer, verify `role="status"` element exists,
313+
content is announced on show, cleared on hide
314+
- If using `aria-label`, verify the label text matches the
315+
tooltip/popover content
316+
5. **Check for `silent` or equivalent opt-out** for cases where the
317+
element's ARIA behavior conflicts with author-provided accessibility
318+
6. **Verify progressive enhancement code** is present:
319+
- IDL property assignment (`ariaDescribedByElements = [el]`) should
320+
be kept even though it silently fails today
321+
- Comment explaining the cross-root limitation and linking to
322+
relevant spec issues
323+
7. **Document the gap** in the review report under "Cross-Browser
324+
Differences" or "Warnings"
325+
326+
### Spec and issue references
327+
328+
- WICG/aom#192 -- cross-root element reflection scope
329+
- whatwg/html#5401 -- element reflection and shadow roots
330+
- whatwg/html#10707 -- Reference Target spec integration (Stage 3)
331+
- WICG/webcomponents#1086 -- Reference Target tracking issue
332+
- WICG/webcomponents#974 -- ElementInternals pointing into own shadow
333+
- WPT `custom-elements/element-internals-aria-element-reflection.html`

0 commit comments

Comments
 (0)