Toegankelijke webcomponenten

Een schaduwgrens verandert de werking van semantiek en ARIA. Er zijn 2 veelvoorkomende fouten: ontbrekende semantiek in de schaduwstructuur en ARIA-verwijzingen die ongemerkt worden verbroken omdat ze geen schaduwroots kunnen overschrijden.

Begin met semantische opmaak: gebruik echte <button>, <nav>, <ul> in de template zodat de meeste semantiek kosteloos is en reserveer ElementInternals/ARIA voor de gaten.

Standaard semantiek via de ElementInternals ARIA-mixin

Stel de rol en ARIA-status van een aangepast element in JS in via ElementInternals in plaats van ARIA-kenmerken naar de host te schrijven. Zo blijven de semantische elementen de standaardwaarden die de component bezit, terwijl een consument ze nog steeds kan overschrijven met kenmerken in de 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);

Zo wordt de DOM niet vervuild met ARIA-kenmerken en kunnen consumenten ze niet per ongeluk overschrijven, terwijl de toegankelijkheidsstructuur nog steeds de juiste rol en status ziet.

Focusbeheer en delegatesFocus

  • Als het hostelement zelf het interactieve bedieningselement is en de rol en status via ElementInternals (zoals ToggleSwitch hierboven) draagt, plaats je de host in de tabvolgorde in connectedCallback (if (!this.hasAttribute('tabindex')) this.tabIndex = 0, nooit in de constructor, die wordt geactiveerd op document.createElement) zodat de focus op de host komt te liggen en ondersteunende technologie de rol en status aankondigt. Stel tabindex niet in op de host voor statische, niet-interactieve componenten (zoals een highlight of kaart).
  • Koppel de shadow root met delegatesFocus: true als de component een intern focusbaar bedieningselement omhult dat de semantiek bezit (of op een aan een formulier gekoppeld aangepast element met static formAssociated = true). Als je de host focust (en op een gekoppeld <label> voor aan een formulier gekoppelde elementen klikt), wordt de focus doorgestuurd naar het eerste focusbare onderliggende element en komt :focus overeen met de host. Combineer delegatesFocus: true niet met een rol/status op de host, anders komt de focus op het binnenste element te liggen en wordt de status van de host overgeslagen.
  • Voor een bewegende focus of een focus die naar een nieuw onthuld gebied wordt verplaatst, geef je het doel tabindex="-1" en roep je .focus() aan. Maak programmatic focusbare doelen expliciet in plaats van te vertrouwen op de tabvolgorde.

ARIA-relaties tussen shadow roots

  • VERPLICHT: Een id waarnaar wordt verwezen door aria-labelledby, aria-describedby, aria-controls, enzovoort, wordt alleen omgezet binnen de eigen boom van het verwijzende element. Een op id gebaseerd kenmerk in de Light DOM kan niet verwijzen naar een id in een schaduwstructuur en omgekeerd. De verwijzing wordt zonder melding genegeerd.
  • Als je de grens wilt overschrijden, gebruik je de gereflecteerde ARIA-element-property's (bijvoorbeeld ariaLabelledByElements, ariaDescribedByElements, enzovoort), die directe JS-elementreferenties gebruiken in plaats van ID's. Ze worden naar buiten opgelost: een element in een schaduwstructuur kan verwijzen naar een doel in de Light DOM of een bovenliggende structuur (waaronder content in een slot). Je kunt dus een schaduwinterne bediening labelen vanuit Light DOM-content, iets wat id-verwijzingen niet kunnen uitdrukken.
  • Ze worden niet naar binnen opgelost: een element kan niet verwijzen naar een onderliggende schaduwstructuur (een host kan bijvoorbeeld niet worden gelabeld door de eigen schaduwcontent). Voor die richting is het opkomende mechanisme Referentiedoel de standaardrichting, maar dit is nog niet breed beschikbaar.
  • Als een relatie niet op een van beide manieren kan worden uitgedrukt, houd je deze binnen één boom (geef het label weer in dezelfde boom) of gebruik je aria-label-tekst in plaats van een id-verwijzing.

Reservestrategieën

Als de ElementInternals ARIA-mixin niet beschikbaar is, stel je de rol en ARIA-status in als kenmerken op de host (this.setAttribute('role', 'switch')). Dit werkt overal, maar gaat ten koste van het plaatsen van kenmerken in de DOM die een consument kan overschrijven.

Als de weerspiegelde ARIA-element-eigenschappen niet beschikbaar zijn, gebruik je een op id gebaseerd aria-*-kenmerk voor relaties in dezelfde boomstructuur. Een label dat de grens overschrijdt, heeft geen op id gebaseerd equivalent, dus val terug op aria-label-tekst.