Problem statement
Currently, when HTML syntax needs to reference another element, this is typically done via id references:
<input type="radio" id="foo">
<label for="foo">Foo</label>
<input list="foo">
<datalist id="foo">
...
</datalist>
See also:
- Literally any nontrivial ARIA markup, e.g. these tabs
<popup>
proposal by Microsoft
This works well for top-level, global things, but less well for repeated structures and deeply nested contexts. Assigning a bunch of ids is also more flimsy, and less portable. Copying and pasting code, the quintessential code reuse method of novices, is not friendly to this method.
Not only is assigning ids an unnecessary overhead for authors, but since all ids create global variables, it even has the potential to break JS code. The more ids that are specified, the higher the overhead to avoid collisions.
Note that Web Components also often need syntax to reference other elements, and HTML does not offer a great precedent for them, since its only element reference mechanism is global ids. Since ids are not always convenient, WC authors end up inventing their own syntax, which can often be suboptimal (I have even seen attributes that specify a …child index).
Also, given ARIA’s heavy need for relationship attributes, the additional overhead of these IDREFs means that authors are more likely to skimp on accessibility.
There should be a way to associate elements based on their relationship in the DOM tree. E.g. so that a <label>
could be associated with its previous sibling without said sibling requiring an id.
Solution 1: Selectors
Selectors + :scope
seem like a natural fit for this, though the full power of selectors may be too much here, and not really needed. Also, for this to work, it would need to be able to look in both directions, whereas selectors only look up the tree. Perhaps a hugely cut down selector grammar involving :has()
like this might work:
<relative-selector> = <element> ':has(' [ <combinator> <element> ]* ')'
<element> = [ <type-selector> | <id-selector> | ':scope' ]
(tokens not defined here are from Selectors Level 4 )
There is also the question of disambiguation: if we keep the same attributes, the new syntax needs to be invalid as an id, or sufficiently rare, for web compat. Or, alternatively, new attributes could be introduced, which would also allow for fallbacks.
Then the label example could become:
<input type="radio">
<label relfor="input:has(+ :scope)">Foo</label>
Solution 2: Relative ids
An alternative solution would be to introduce a mechanism for relative ids, which are scoped to the closest ancestor scope with an id or relative name. E.g.:
<div prop="foo">
<input type="radio" prop="bar">
<label for="bar">Bar 1</label>
</div>
<div prop="foo">
<input type="radio" prop="bar">
<label for="bar">Bar 2</label>
</div>
Such a mechanism could also be useful for other Web Platform languages, e.g. CSS also often needs element references, and has the same problem with ids (e.g. see element()
).
Solution 3: Keywords for common cases
A lot of the use cases for relative references only need referencing a previous or next sibling. If a more general solution is infeasible (or lacks implementer interest), introducing keywords for these common cases would address a lot of author needs. Disambiguation may be more of a problem in that case, as it doesn’t make sense to introduce entirely new attributes for this.