Accesibilidad
La accesibilidad se reparte entre el framework y la página. iVOLT puede darte foco visible, colores con contraste, comportamiento de teclado y estado ARIA correcto en sus propios componentes. No puede darte nombres accesibles, un orden de títulos sensato ni etiquetas con sentido: eso lo escribe quien construye la página. Esta página traza esa línea y dice qué se ha probado de verdad.
La afirmación que hacemos, y la única: las fixtures evaluadas no presentan hallazgos graves ni críticos de axe en ninguno de los dos temas, y pasan una revisión manual de teclado. Eso no es una certificación de nada.
De qué se encarga el framework
Foco visible
El foco es un token, no una decisión de cada componente. .iv-root :focus-visible dibuja var(--iv-focus-width) (2px) de solid var(--iv-color-focus) con var(--iv-focus-offset) (2px) de separación, y los componentes que necesitan otra colocación vuelven a declarar los mismos tokens en vez de inventarse un anillo: el elemento del menú desplegable, por ejemplo, usa una separación negativa para que el contorno quede dentro del menú desplazable en lugar de recortarse.
El color de foco es #1D4FC4 en claro y #73DFFF en oscuro, ambos en 3:1 o por encima sobre cualquiera de las dos superficies. Nada del paquete pone outline: none sin sustituirlo. Usar :focus-visible en vez de :focus significa que un clic de ratón sobre un botón no deja anillo, mientras que ese mismo botón enfocado con Tab sí lo deja.
<div class="iv-cluster">
<button class="iv-button iv-button--primary" type="button" disabled>Disabled</button>
<a class="iv-button iv-button--secondary" href="#docs" aria-disabled="true">Disabled link</a>
<button class="iv-button iv-button--primary" type="button" aria-busy="true">Saving…</button>
</div>Color y contraste
Los tokens semánticos se eligieron midiendo ratios, no a ojo. Los valores de abajo son el contraste previsto para los temas claro y oscuro, medido sobre bg salvo cuando se indica otra cosa.
| Token | Claro | Oscuro | Contraste (claro / oscuro) |
|---|---|---|---|
text | #0B1230 | #EEF2FB | 18.4 / 18.0 |
text-muted | #4F5B7A | #A8B3CC | 6.8 / 9.6 (8.7 sobre surface-raised) |
border-strong (controls) | #6B7896 | #6B7896 | 4.4 / 4.6 (≥ 3:1) |
border (decorative) | #D3DAEA | #1C2544 | no se exige |
primary / on-primary | #1D4FC4 / #FFFFFF | #66B1FF / #06122B | 7.1 / 8.9 |
accent / on-accent | #0B6B8A / #FFFFFF | #73DFFF / #06122B | 5.5 / 13.2 |
success / on-success | #4F7A12 / #FFFFFF | #8BBF3A / #06122B | 5.1 / 9.2 |
danger / on-danger | #D63A31 / #FFFFFF | #FF9AA4 / #06122B | 4.7 / 10.0 |
warning / on-warning | #8F5C00 / #FFFFFF | #FBBF24 / #06122B | 5.0 / 12.1 |
info / on-info | #3D47C2 / #FFFFFF | #8B95FF / #06122B | 7.3 / 7.5 |
focus | #1D4FC4 | #73DFFF | ≥ 3:1 en ambas superficies |
El peor caso real es surface-raised, donde border-strong mide 4,13 en oscuro y 4,12 sobre surface en claro; los dos quedan muy por encima de 3:1. Cada fondo *-subtle va emparejado con su propio primer plano y se midió contra él, así que una alerta o una insignia nunca es una superficie de color con texto ilegible encima.
Hay una consecuencia que conviene conocer: primary y focus comparten el mismo cobalto, y en el tema oscuro focus coincide con accent. Es intencionado: el foco y el primario se distinguen por la forma (una insignia o una alerta con fondo suave, un anillo de foco separado), no por el tono. Si tu interfaz necesita separarlos por color, redefine --iv-color-success. Y en general, no dejes nunca que el color sea lo único que transmite un significado: un estado de error necesita texto, un icono o ambos.
Estas cifras describen los tokens. Si sobrescribes un token con el color de tu marca, dejan de valer: vuelve a medir.
Tamaño de las áreas interactivas
Las áreas interactivas apuntan a un mínimo de 24 × 24 píxeles CSS. El botón de solo icono se dispone como un cuadrado que no baja de ese tamaño en ninguna de sus medidas, y las áreas en línea compactas, como los enlaces de la miga de pan, llevan un min-block-size de 1.5rem. Si construyes tu propio control, conserva el relleno que lo lleva hasta ahí; encoger un botón con tu propio CSS puede dejarlo por debajo del umbral.
Teclado y ARIA en los componentes con JavaScript
Seis familias necesitan JavaScript: desplegable, pestañas, diálogo, panel lateral, menú desplegable y aviso. init(document) — llamado de forma explícita o al cargar a través de la entrada auto — encuentra los elementos marcados con data-iv-component y se hace cargo de su teclado y de su estado ARIA. Los roles y estados que necesita se añaden cuando faltan, de modo que el marcado que escribes sigue siendo pequeño, y destroy() restaura los atributos exactamente como los encontró.
El vocabulario de teclado compartido es el de la plataforma: Tab para moverse, Enter y Espacio para activar, Escape para descartar lo que se puede descartar, y las flechas con Inicio y Fin para moverse dentro de un widget compuesto como las pestañas o un menú. Cada página de componente documenta sus propias teclas; esa es la lista de referencia.
Dos decisiones estructurales ayudan aquí. El diálogo y el panel lateral son elementos <dialog> de verdad, así que la contención del foco en modal, Escape y la capa superior vienen del navegador y no de una trampa de foco escrita a mano. El menú desplegable se construye sobre <details>, así que se abre y se cierra con el teclado incluso antes de que el script se ejecute. A la región de avisos se le da role="region" cuando no tiene ninguno, porque un elemento etiquetado sin rol no anuncia nada.
Preferencias de movimiento y contraste
@media (prefers-reduced-motion: reduce) pone --iv-motion-fast, --iv-motion-base y --iv-motion-slow a 0ms, y los componentes que animan — botón, diálogo, menú desplegable, progreso y esqueleto — además abandonan sus transformaciones y sus animaciones en bucle dentro de la misma consulta. Los estados siguen siendo perfectamente visibles: la reducción de movimiento quita el movimiento, no la respuesta visual.
Con prefers-contrast: more, la hoja de tokens sube los bordes decorativos (--iv-color-border) al valor de los controles (--iv-color-border-strong), de modo que los bordes de tarjetas y tablas llegan a 3:1. No cambia nada más; el modo de colores forzados está sin probar en esta fase.
De qué se encarga quien escribe la página
Estos son los fallos que aparecen en los proyectos reales construidos sobre cualquier framework, iVOLT incluido.
- Nombres accesibles. Un botón de solo icono es un botón sin etiqueta hasta que le das
aria-labelo un texto oculto visualmente coniv-u-sr-only. Lo mismo vale para un botón de cerrar, un disparador de menú y un enlace de icono. - Orden de los títulos. Un
<h1>por página y sin saltarse niveles. En iVOLT el tamaño del título es un token, así que usa el nivel que pida el documento y cambia su tamaño con una utilidad si te parece demasiado grande. - Etiquetas, no placeholders. Todo campo necesita una
<label for>de verdad. Un placeholder desaparece en cuanto alguien escribe. - Textos de ayuda y de error. Conéctalos con
aria-describedbypara que se anuncien junto al campo, y marca el campo conaria-invalid="true"cuando esté en error. El texto de error tiene que decir qué hacer, no solo que algo va mal. - Una alternativa sin JavaScript. Las familias de solo CSS funcionan sin ningún script. Para las interactivas, escribe marcado que degrade:
<details>para el desplegable y el menú, una alternativa con:targetpara el diálogo y el panel lateral, paneles de pestañas todos visibles antes de que el script los promocione. Si el script no llega a cargarse, el contenido tiene que seguir siendo alcanzable. - Idioma y puntos de referencia.
langen<html>, un<main>, un enlace para saltar al contenido yaria-labelen los puntos de referencia repetidos, como dos navegaciones. - Contraste del contenido. El texto que coloques sobre una imagen, un degradado o un color de tu marca queda fuera de la tabla de tokens.
<div class="iv-stack" style="max-width: 28rem">
<div class="iv-field">
<label class="iv-label" for="f-email">Work email</label>
<input class="iv-input" id="f-email" type="email" value="dave@" aria-invalid="true" aria-describedby="f-email-error" autocomplete="email">
<p class="iv-field__error iv-u-m-0" id="f-email-error">Enter a complete email address, like [email protected].</p>
</div>
<div class="iv-field">
<label class="iv-label" for="f-locked">Plan</label>
<input class="iv-input" id="f-locked" type="text" value="Team (locked)" disabled>
</div>
</div>Cómo se prueba
| Comprobación | Alcance |
|---|---|
| axe-core a través de Playwright | 24 fixtures × temas claro y oscuro, más el diálogo abierto; etiquetas wcag2a wcag2aa wcag21aa wcag22aa; la puerta falla ante cualquier hallazgo grave o crítico |
| Revisión manual de teclado | Todas las familias interactivas: llegar, activar, descartar y dónde queda el foco después |
| Tres motores | La suite de navegador se ejecuta en Chromium, Firefox y WebKit (IVOLT_ALL_BROWSERS=1) |
| Desbordamiento y RTL | De 320 a 1920 px sin desbordamiento horizontal, y renderizado de derecha a izquierda, porque las propiedades lógicas son lo que hace que eso funcione |
| Las fixtures como fuente única | El mismo archivo de fixture es la vista previa de la documentación, el fragmento copiable y el sujeto de la prueba, así que lo que se prueba es lo que se documenta |
Las herramientas automáticas detectan una minoría de los problemas. axe no puede decirte que una etiqueta está mal, que un título miente sobre la estructura o que un orden de foco es confuso; para eso está la revisión manual, y es una revisión, no una demostración.
Qué no afirmamos
- Ni «100 % accesible», ni «certificado WCAG», ni «conforme» como propiedad del framework. La conformidad es una propiedad de una página terminada, y la mayor parte de esa página es tuya.
- Las pruebas con lector de pantalla no se han hecho. Las pasadas con NVDA y VoiceOver están en la lista de pendientes como punto P1 y constan como no verificadas. Hasta entonces, los anuncios se deducen del ARIA que ponemos, no se han observado.
- Sin ejecución de Lighthouse todavía; está prevista para una fase posterior.
- La cobertura de pruebas es Chromium, Firefox y WebKit a través de Playwright, que no es lo mismo que probar dispositivos físicos, tecnologías asistivas o navegadores antiguos.
- Los hallazgos en tus propias páginas no quedan cubiertos por nada de lo anterior. Ejecuta axe sobre la página que publiques.
Si encuentras un fallo de accesibilidad en un componente o en una fixture, es un error: abre una incidencia con el nombre de la fixture, el navegador y los pasos.