Um limite de sombra muda o funcionamento da semântica e do ARIA. Duas falhas são mais comuns: semântica ausente dentro da árvore de sombra e referências de ARIA que falham silenciosamente porque não podem cruzar raízes de sombra.
Comece com a marcação semântica: use <button>, <nav> e <ul> reais dentro do modelo para que a maior parte da semântica seja fornecida sem custo financeiro e reserve ElementInternals/ARIA para as lacunas.
Semântica padrão pelo mixin ARIA ElementInternals
Defina o papel e o estado ARIA de um elemento personalizado em JS usando ElementInternals, em vez de gravar atributos ARIA no host. Isso mantém a semântica como padrão do componente, mas permite que um consumidor a substitua por atributos no DOM leve.
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);
Isso evita poluir o DOM com atributos ARIA e impede que os consumidores os substituam acidentalmente, enquanto a árvore de acessibilidade ainda vê o papel e o estado corretos.
Gerenciamento de foco e delegatesFocus
- Quando o próprio elemento host é o controle interativo e carrega a função e o estado por meio de
ElementInternals(comoToggleSwitchacima), coloque o host na ordem de tabulação dentro deconnectedCallback(if (!this.hasAttribute('tabindex')) this.tabIndex = 0— nunca no construtor, que gera um erro emdocument.createElement) para que o foco seja direcionado ao host e a tecnologia adaptativa anuncie a função e o estado dele. Não definatabindexno host para componentes estáticos e não interativos (como um balão ou card). - Anexe a raiz de sombra com
delegatesFocus: truequando o componente envolver um controle focalizável interno que tenha a semântica (ou em um elemento personalizado associado ao formulário comstatic formAssociated = true). Ao focar o host (e clicar em um<label>associado para elementos associados ao formulário), o foco é encaminhado para o primeiro filho focalizável, e:focuscorresponde ao host. Não combinedelegatesFocus: truecom uma função/estado no host, ou o foco vai ficar no elemento interno e pular o estado do host. - Para foco móvel ou para mover o foco para uma região recém-revelada, atribua
tabindex="-1"ao destino e chame.focus(). Torne os destinos programaticamente focalizáveis explícitos em vez de depender da ordem de tabulação.
Relações ARIA em raízes de sombra
- OBRIGATÓRIO: um
idreferenciado poraria-labelledby,aria-describedby,aria-controlsetc. é resolvido apenas na própria árvore do elemento de referência. Um atributo baseado emidno DOM leve não pode apontar para umiddentro de uma árvore de sombra e vice-versa. A referência é ignorada silenciosamente. - Para cruzar o limite, use as propriedades de elementos ARIA refletidas (por exemplo,
ariaLabelledByElements,ariaDescribedByElementsetc.), que usam referências diretas de elementos JS em vez de IDs. Eles são resolvidos para fora: um elemento dentro de uma árvore de sombra pode apontar para um destino no DOM de luz ou em qualquer árvore ancestral (incluindo conteúdo com slot). Assim, é possível rotular um controle interno de sombra do conteúdo do DOM de luz, algo que as referências deidnão podem expressar. - Eles não são resolvidos para dentro: um elemento não pode fazer referência a uma árvore de sombra descendente (por exemplo, um host não pode ser rotulado pelo próprio conteúdo de sombra). Para essa direção, o mecanismo emergente de destino de referência é a direção dos padrões, mas ainda não está amplamente disponível.
- Quando uma relação não puder ser expressa de nenhuma das duas formas, mantenha-a em uma árvore (renderize o rótulo na mesma árvore) ou use o texto
aria-labelem vez de uma referênciaid.
Estratégias de fallback
Quando o mixin ARIA ElementInternals não estiver disponível, defina o papel e o estado ARIA como atributos no host (this.setAttribute('role', 'switch')). Isso funciona em qualquer lugar, mas coloca atributos no DOM que um consumidor pode substituir.
Quando as propriedades de elementos ARIA refletidas não estiverem disponíveis, use um atributo aria-* baseado em id para relações na mesma árvore. Um rótulo entre limites não tem um equivalente baseado em id, então use o texto aria-label.