Une limite de shadow DOM modifie le fonctionnement de la sémantique et d'ARIA. Deux échecs dominent : l'absence de sémantique dans l'arborescence Shadow et les références ARIA qui se rompent silencieusement, car elles ne peuvent pas traverser les racines Shadow.
Commencez par le balisage sémantique : utilisez de vrais éléments <button>, <nav> et <ul> dans le modèle afin que la plupart des éléments sémantiques soient inclus, et réservez ElementInternals/ARIA pour les lacunes.
Sémantique par défaut via le mixin ARIA ElementInternals
Définissez le rôle et l'état ARIA d'un élément personnalisé en JS via ElementInternals, plutôt que d'écrire des attributs ARIA sur l'hôte. Cela permet de conserver les sémantiques par défaut du composant, tout en permettant à un consommateur de les remplacer par des attributs dans le Light DOM.
class ToggleSwitch extends HTMLElement {
#internals;
constructor() {
super();
this.#internals = this.attachInternals();
this.#internals.role = 'switch'; // default role, no host attribute needed
this.#internals.ariaChecked = 'false'; // exposed to the accessibility tree
this.addEventListener('click', () => this.toggle());
this.addEventListener('keydown', (e) => {
if (e.key === ' ') { e.preventDefault(); this.toggle(); }
});
}
connectedCallback() {
// Put the host in the tab order on connect (setting tabIndex in the constructor throws on createElement).
if (!this.hasAttribute('tabindex')) this.tabIndex = 0;
}
toggle() {
const on = this.#internals.ariaChecked !== 'true';
this.#internals.ariaChecked = String(on);
}
}
customElements.define('toggle-switch', ToggleSwitch);
Cela évite de polluer le DOM avec des attributs ARIA et empêche les utilisateurs de les écraser accidentellement, tandis que l'arborescence d'accessibilité voit toujours le rôle et l'état corrects.
Gestion du focus et delegatesFocus
- Lorsque l'élément hôte lui-même est le contrôle interactif et qu'il porte le rôle et l'état via
ElementInternals(commeToggleSwitchci-dessus), placez l'hôte dans l'ordre de tabulation dansconnectedCallback(if (!this.hasAttribute('tabindex')) this.tabIndex = 0, jamais dans le constructeur, qui génère une exception surdocument.createElement) afin que le curseur se place sur l'hôte et que la technologie d'assistance annonce son rôle et son état. Ne définissez pastabindexsur l'hôte pour les composants statiques non interactifs (comme une légende ou une fiche). - Associez la racine du shadow DOM avec
delegatesFocus: truelorsque le composant encapsule un contrôle interne sélectionnable qui possède la sémantique (ou sur un élément personnalisé associé à un formulaire avecstatic formAssociated = true). Le fait de sélectionner l'hôte (et de cliquer sur un<label>associé pour les éléments associés à un formulaire) transmet ensuite la sélection au premier enfant sélectionnable, et:focuscorrespond à l'hôte. Ne combinez pasdelegatesFocus: trueavec un rôle/état sur l'hôte, sinon le focus se portera sur l'élément interne et ignorera l'état de l'hôte. - Pour déplacer le focus ou le déplacer vers une région nouvellement révélée, attribuez à la cible
tabindex="-1"et appelez.focus(). Rendez les cibles sélectionnables par programmation explicites plutôt que de vous fier à l'ordre de tabulation.
Relations ARIA entre les racines fantômes
- OBLIGATOIRE : un
idréférencé pararia-labelledby,aria-describedby,aria-controls, etc. n'est résolu que dans l'arborescence propre de l'élément de référence. Un attribut basé suriddans le Light DOM ne peut pas pointer vers uniddans un arbre fantôme, et inversement. La référence est ignorée sans message d'erreur. - Pour franchir la limite, utilisez les propriétés d'élément ARIA reflétées (par exemple,
ariaLabelledByElements,ariaDescribedByElements, etc.), qui prennent des références d'élément JS directes au lieu d'ID. Ils se résolvent vers l'extérieur : un élément d'une arborescence Shadow peut pointer vers une cible dans le Light DOM ou n'importe quelle arborescence ancêtre (y compris le contenu inséré). Vous pouvez ainsi étiqueter un contrôle interne au Shadow à partir du contenu Light DOM, ce que les référencesidne peuvent pas exprimer. - Ils ne se résolvent pas vers l'intérieur : un élément ne peut pas référencer dans un shadow tree descendant (par exemple, un hôte ne peut pas être étiqueté par son propre shadow content). Pour cette direction, le mécanisme émergent Reference Target est la direction standard, mais il n'est pas encore largement disponible.
- Lorsqu'une relation ne peut pas être exprimée d'une manière ou d'une autre, conservez-la dans une seule arborescence (affichez le libellé dans la même arborescence) ou utilisez du texte
aria-labelau lieu d'une référenceid.
Stratégies de remplacement
Lorsque le mixin ARIA ElementInternals n'est pas disponible, définissez le rôle et l'état ARIA en tant qu'attributs sur l'hôte (this.setAttribute('role', 'switch')). Cela fonctionne partout, mais cela implique de placer des attributs dans le DOM qu'un consommateur pourrait remplacer.
Lorsque les propriétés d'élément ARIA reflétées ne sont pas disponibles, utilisez un attribut aria-* basé sur id pour les relations dans la même arborescence. Un libellé inter-limites n'a pas d'équivalent basé sur id. Vous devez donc utiliser le texte aria-label.