Componenti web accessibili

Un confine shadow modifica il funzionamento della semantica e di ARIA. I problemi principali sono due: la semantica mancante all'interno dello shadow tree e i riferimenti ARIA che si interrompono senza preavviso perché non possono attraversare le shadow root.

Inizia dal markup semantico: utilizza i tag <button>, <nav>, <ul> reali all'interno del template in modo che la maggior parte della semantica sia già presente e riserva ElementInternals/ARIA per le lacune.

Semantica predefinita tramite il mixin ARIA ElementInternals

Imposta il ruolo e lo stato ARIA di un elemento personalizzato in JS tramite ElementInternals, anziché scrivere attributi ARIA sull'host. In questo modo, la semantica viene mantenuta come impostazione predefinita del componente, consentendo comunque a un consumer di eseguirne l'override con gli attributi nel DOM Light.

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);

In questo modo si evita di inquinare il DOM con attributi ARIA e si impedisce ai consumatori di sovrascriverli accidentalmente, mentre l'albero di accessibilità vede comunque il ruolo e lo stato corretti.

Gestione del focus e delegatesFocus

  • Quando l'elemento host stesso è il controllo interattivo e trasporta il ruolo e lo stato tramite ElementInternals (come ToggleSwitch sopra), inserisci l'host nell'ordine di tabulazione all'interno di connectedCallback (if (!this.hasAttribute('tabindex')) this.tabIndex = 0, mai nel costruttore, che genera un errore su document.createElement) in modo che lo stato attivo si trovi sull'host e la tecnologia per la disabilità annunci il suo ruolo e il suo stato. Non impostare tabindex sull'host per i componenti statici non interattivi (come un callout o una scheda).
  • Collega la shadow root con delegatesFocus: true quando il componente include un controllo attivabile interno che possiede la semantica (o su un elemento personalizzato associato al modulo con static formAssociated = true). L'attivazione dell'host (e il clic su un <label> associato per gli elementi associati al modulo) inoltra l'attivazione al primo elemento secondario attivabile e :focus corrisponde all'host. Non combinare delegatesFocus: true con un ruolo/stato sull'host, altrimenti lo stato attivo verrà applicato all'elemento interno e lo stato dell'host verrà ignorato.
  • Per lo stato attivo mobile o per spostare lo stato attivo in una regione appena visualizzata, assegna al target tabindex="-1" e chiama .focus(); rendi espliciti i target attivabili tramite programmazione anziché fare affidamento sull'ordine di tabulazione.

Relazioni ARIA tra shadow root

  • OBBLIGATORIO: un id a cui fanno riferimento aria-labelledby, aria-describedby, aria-controls e così via viene risolto solo all'interno dell'albero proprio dell'elemento di riferimento. Un attributo basato su id nel light DOM non può puntare a un id all'interno di uno shadow tree e viceversa; il riferimento viene ignorato senza avvisi.
  • Per superare il confine, utilizza le proprietà ARIA element riflesse (ad es. ariaLabelledByElements, ariaDescribedByElements e così via), che accettano riferimenti diretti agli elementi JS anziché agli ID. Vengono risolti verso l'esterno: un elemento all'interno di uno shadow tree può puntare a un target nel Light DOM o in qualsiasi albero predecessore (inclusi i contenuti con slot), in modo da poter etichettare un controllo interno allo shadow dai contenuti del Light DOM, un'operazione che i riferimenti id non possono esprimere.
  • Non si risolvono verso l'interno: un elemento non può fare riferimento all'interno di uno shadow tree secondario (ad esempio, un host non può essere etichettato dai propri contenuti shadow). In questa direzione, il meccanismo emergente Reference Target è la direzione degli standard, ma non è ancora ampiamente disponibile.
  • Quando una relazione non può essere espressa in nessuno dei due modi, mantienila all'interno di un albero (esegui il rendering dell'etichetta nello stesso albero) o utilizza il testo aria-label anziché un riferimento id.

Strategie di riserva

Se il mixin ARIA ElementInternals non è disponibile, imposta il ruolo e lo stato ARIA come attributi sull'host (this.setAttribute('role', 'switch')). Funziona ovunque, al costo di inserire nel DOM attributi che un consumer potrebbe sostituire.

Se le proprietà degli elementi ARIA riflesse non sono disponibili, utilizza un attributo aria-* basato su id per le relazioni nello stesso albero. Un'etichetta tra confini non ha un equivalente basato su id, quindi utilizza il testo aria-label.