Pruebas de Elementos Personalizados que Usan ElementInternals

This page is not available in the language you requested. You have been redirected to the English version of the page.
Link to this page copied to clipboard

Cómo Axe DevTools para Web lee semánticas ARIA desde ElementInternals, qué deben hacer tus componentes para exponerlas y qué lenguajes lo soportan.

Not for use with personal data

Los componentes web pueden declarar sus semánticas de accesibilidad en JavaScript en lugar de en el marcado, llamando a attachInternals() y configurando propiedades como role y ariaLabel en el objeto ElementInternals devuelto. Un componente construido de esta manera no tiene atributos role o aria-* en el DOM, por lo que una herramienta de prueba que solo lee atributos ve un elemento sin semánticas en absoluto.

Axe DevTools para Web ahora lee estas semánticas. Esta página explica qué versiones lo soportan, qué deben hacer tus componentes para hacer visibles sus internos y cómo interactúan los internos con los atributos ya presentes en el elemento. Para información general sobre cómo seleccionar tu versión de axe-core, consulta Personalización de Reglas.

Lenguajes y Versiones Soportados

El soporte proviene del motor de pruebas de accesibilidad subyacente, por lo que está disponible dondequiera que la versión agrupada de axe-core sea 4.13.0 o superior.

Lenguaje Versión que añadió soporte Axe-core agrupado
C# 4.13.0 4.13.0
Python 4.13.0 4.13.0
Ruby 4.13.0 4.13.0 (a través de axe-core-api 4.13.0)
Java 4.13.0 4.13.0 (a través de com.deque.html.axe-core 4.13.0)
Node.js y la CLI 4.13.0 4.13.0
note

No hay nada que activar. En cada lenguaje mencionado anteriormente, el motor de pruebas de accesibilidad se ejecuta directamente en la página bajo prueba, por lo que lee ElementInternals en cada análisis sin ninguna opción, bandera o cambio de configuración. Si tus componentes ya siguen el protocolo descrito a continuación, actualizar es lo único necesario.

Exponiendo ElementInternals a Axe DevTools

ElementInternals es privado por diseño: JavaScript no proporciona una API para leer los internos de otro elemento. Una herramienta de prueba solo puede ver el objeto si tu componente lo publica deliberadamente. Los autores de componentes hacen esto a través de un protocolo de la comunidad, y Axe DevTools soporta cada forma de ese protocolo.

La forma recomendada es un WeakMap global, que mantiene el objeto fuera del propio 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);
    }
  }
);

Asignar el objeto a una propiedad en el elemento también funciona:

customElements.define(
  'my-custom-button',
  class MyCustomButton extends HTMLElement {
    constructor() {
      super();

      this._internals = this.attachInternals();
      this._internals.role = 'button';
    }
  }
);

Además del mapa globalThis._elementInternals, Axe DevTools busca el objeto bajo cualquiera de estos nombres en el elemento:

  • _internals (recomendado)
  • internals
  • internals_
  • Symbol('internals')
  • Symbol('privateInternals')
important

Un componente que mantiene sus internos genuinamente privados es invisible para Axe DevTools, y esta es la razón más común por la que no se ve ningún cambio después de la actualización. Dos casos a revisar:

  • Campos de clase privados. this.#internals = this.attachInternals() no se puede leer desde fuera de la clase, por lo que ninguna herramienta de prueba puede verlo. Varias bibliotecas de componentes usan este patrón.
  • Getters. Una propiedad definida como un accesor en lugar de un valor simple es omitida, incluso si está nombrada _internals. Asigna el objeto directamente.

En ambos casos, la solución pertenece al componente, no a tu configuración de prueba. Publica el objeto a través de una de las formas mencionadas, o espera que el elemento sea evaluado como si no tuviera semánticas ARIA.

Probar que tus componentes son visibles para Axe DevTools no requiere un análisis. En la consola del navegador en una página que use el componente, globalThis._elementInternals?.get(document.querySelector('my-custom-button')) o document.querySelector('my-custom-button')._internals debería devolver un objeto ElementInternals en lugar de undefined.

Lo que Lee Axe DevTools

Una vez que el objeto es visible, Axe DevTools lee la propiedad role y las propiedades ARIA que corresponden a los atributos aria-*, como ariaLabel para aria-label, ariaDescription para aria-description, y ariaLabelledByElements para aria-labelledby. Estos valores alimentan las mismas reglas que habrían corrido contra los atributos equivalentes, así que un elemento personalizado que configura role y ariaLabel a través de internos es verificado para un nombre accesible de la misma manera que uno que configura role y aria-label en el marcado.

Precedencia

Los valores presentes en el DOM siempre ganan. Esto significa que añadir atributos a tu marcado es una manera confiable de anular lo que un componente declara internamente, y que los internos nunca enmascaran un problema visible en el DOM.

  • Rol. El role de ElementInternals es tratado como el rol implícito del elemento, el mismo estatus que el rol incorporado de un elemento nativo. Un atributo role explícito en el elemento lo sobrescribe.
  • Valores ARIA. Cada valor se resuelve primero desde el atributo, luego la propiedad correspondiente en el elemento, y solo después desde ElementInternals.

Limitaciones

El soporte es real pero aún no está completo, y las lagunas son importantes de conocer antes de interpretar un análisis.

Las reglas que seleccionan atributos no coinciden con elementos exclusivamente internos. Varias reglas identifican los elementos a los que se aplican con un selector CSS contra el DOM. Debido a que un elemento cuyas características provienen solo de ElementInternals no tiene un atributo role, esas reglas nunca se aplican a él. Por ejemplo, aria-required-attr selecciona [role], y aria-command-name selecciona [role="link"], [role="button"], [role="menuitem"]. Un elemento personalizado que declara role = 'button' a través de internals no es evaluado por ninguna de las reglas. Las reglas que resuelven el rol en lugar de coincidir con un selector, como aquellas que calculan un nombre accesible, sí se aplican.

Los valores de roles no son validados. Un role establecido a través de internals se usa tal como está. Un valor inválido o mal escrito no se reporta de la misma manera que lo haría un atributo role inválido.

Algunas reglas solo se aplican parcialmente. Las reglas que inspeccionan la relación de un elemento con sus hijos o ancestros, como aria-required-children, pueden no evaluar completamente un elemento exclusivamente interno.

Debido a la primera limitación en particular, espere que un componente que declara sus características solo a través de internals produzca menos hallazgos que el marcado equivalente lo haría, en lugar de más. Un análisis limpio de tal componente es una evidencia más débil que un análisis limpio de uno que use atributos.