Testando Elementos Personalizados que Usam ElementInternals
Como o Axe DevTools for Web lê semânticas ARIA de ElementInternals, o que seus componentes devem fazer para expô-las e quais linguagens são suportadas.
Componentes web podem declarar suas semânticas de acessibilidade em JavaScript em vez de em marcação, chamando attachInternals() e definindo propriedades como role e ariaLabel no objeto ElementInternals retornado. Um componente construído desta forma não possui atributos role ou aria-* no DOM, então uma ferramenta de teste que apenas lê atributos vê um elemento sem semântica alguma.
O Axe DevTools for Web agora lê essas semânticas. Esta página explica quais versões a suportam, o que seus componentes devem fazer para tornar seus internos visíveis, e como os internos interagem com os atributos já presentes no elemento. Para informações gerais sobre selecionar sua versão do axe-core, veja Personalizando Regras.
Linguagens e Versões Suportadas
O suporte vem do motor de testes de acessibilidade subjacente, portanto, está disponível em qualquer lugar onde a versão do axe-core embutida seja 4.13.0 ou superior.
| Linguagem | Versão que adicionou suporte | Axe-core embutido |
|---|---|---|
| C# | 4.13.0 | 4.13.0 |
| Python | 4.13.0 | 4.13.0 |
| Ruby | 4.13.0 | 4.13.0 (através de axe-core-api 4.13.0) |
| Java | 4.13.0 | 4.13.0 (através de com.deque.html.axe-core 4.13.0) |
| Node.js e a CLI | 4.13.0 | 4.13.0 |
Não há nada para ativar. Em todas as linguagens acima, o motor de testes de acessibilidade é executado diretamente na página em teste, então ele lê ElementInternals em cada varredura sem nenhuma opção, sinalizador ou alteração de configuração. Se seus componentes já seguem o protocolo descrito abaixo, a atualização é tudo o que é necessário.
Expondo ElementInternals para o Axe DevTools
ElementInternals é privado por design: JavaScript não fornece API para ler os internos de outro elemento. Uma ferramenta de teste só pode ver o objeto se seu componente deliberadamente publicá-lo. Os autores de componentes fazem isso através de um protocolo da comunidade, e o Axe DevTools suporta todas as formas desse protocolo.
A forma recomendada é um WeakMap global, que mantém o objeto fora do próprio elemento:
customElements.define(
'my-custom-button',
class MyCustomButton extends HTMLElement {
constructor() {
super();
const internals = this.attachInternals();
internals.role = 'button';
globalThis._elementInternals ??= new WeakMap();
globalThis._elementInternals.set(this, internals);
}
}
);Atribuir o objeto a uma propriedade no elemento também funciona:
customElements.define(
'my-custom-button',
class MyCustomButton extends HTMLElement {
constructor() {
super();
this._internals = this.attachInternals();
this._internals.role = 'button';
}
}
);Além do mapa globalThis._elementInternals, o Axe DevTools procura pelo objeto sob qualquer um desses nomes no elemento:
_internals(recomendado)internalsinternals_Symbol('internals')Symbol('privateInternals')
Um componente que mantém seus internos genuinamente privados é invisível para o Axe DevTools, e esta é a razão mais comum para não ver mudanças após a atualização. Dois casos a verificar:
- Campos de classe privados.
this.#internals = this.attachInternals()não podem ser lidos de fora da classe, então nenhuma ferramenta de teste pode vê-los. Várias bibliotecas de componentes usam esse padrão. - Getters. Uma propriedade que é definida como um acessor ao invés de um valor simples é ignorada, mesmo que tenha o nome
_internals. Atribua o objeto diretamente.
Em ambos os casos, a correção pertence ao componente, não à configuração do seu teste. Publique o objeto através de uma das formas acima, ou espere que o elemento seja avaliado como se não tivesse semânticas ARIA.
Testar se seus componentes são visíveis para o Axe DevTools não precisa de uma varredura. No console do navegador, em uma página que usa o componente, globalThis._elementInternals?.get(document.querySelector('my-custom-button')) ou document.querySelector('my-custom-button')._internals devem retornar um objeto ElementInternals em vez de undefined.
O que o Axe DevTools Lê
Uma vez que o objeto está visível, o Axe DevTools lê a propriedade role e as propriedades ARIA que correspondem aos atributos aria-*, como ariaLabel para aria-label, ariaDescription para aria-description e ariaLabelledByElements para aria-labelledby. Esses valores alimentam as mesmas regras que teriam sido aplicadas aos atributos equivalentes, então um elemento personalizado que define role e ariaLabel através de internos é verificado para um nome acessível da mesma forma como um que define role e aria-label em marcação.
Precedência
Os valores presentes no DOM sempre prevalecem. Isso significa que adicionar atributos à sua marcação é uma maneira confiável de sobrescrever o que um componente declara internamente, e que os internos nunca mascaram um problema que é visível no DOM.
- Papel. O
roledeElementInternalsé tratado como o papel implícito do elemento, possuindo a mesma posição que o papel embutido de um elemento nativo. Um atributoroleexplícito no elemento o sobrescreve. - Valores ARIA. Cada valor é resolvido primeiro a partir do atributo, depois da propriedade correspondente no elemento, e somente então de
ElementInternals.
Limitações
O suporte é real, mas ainda não está completo, e as lacunas valem a pena ser conhecidas antes de você interpretar uma verificação.
Regras que selecionam atributos não correspondem a elementos apenas internos. Várias regras identificam os elementos aos quais se aplicam usando um seletor CSS no DOM. Como um elemento cuja semântica provém apenas de ElementInternals não possui atributo role, essas regras nunca se aplicam a ele. Por exemplo, aria-required-attr seleciona [role], e aria-command-name seleciona [role="link"], [role="button"], [role="menuitem"]. Um elemento personalizado que declara role = 'button' por meio de elementos internos não é avaliado por nenhuma das regras. Regras que resolvem o papel ao invés de corresponder a um seletor, como aquelas que calculam um nome acessível, são aplicadas.
Os valores de função não são validados. Um role definido por meio de elementos internos é usado conforme fornecido. Um valor inválido ou escrito incorretamente não é relatado da mesma maneira que um atributo role inválido seria.
Algumas regras são aplicadas apenas parcialmente. Regras que inspecionam a relação de um elemento com seus filhos ou ancestrais, como aria-required-children, podem não avaliar completamente um elemento apenas interno.
Por causa da primeira limitação em particular, espere que um componente que declara sua semântica apenas por meio de elementos internos produza menos resultados do que o equivalente em markup produziria, em vez de mais. Uma verificação limpa de tal componente é uma evidência mais fraca do que uma verificação limpa de um usando atributos.
Páginas Relacionadas
- Personalizando Regras para selecionar a versão do axe-core que suas verificações usam.
- Sobre Axe DevTools para APIs Web para a lista completa de idiomas e frameworks suportados.
- Documentação da API C#, Visão geral do Python, Uso Avançado da API com Ruby, Visão geral da API Java, e Testes baseados em Node.js e Browser para as ligações de linguagem que suportam isso.
