Guía de accesibilidad y mejores prácticas
Esta guía ha sido creada para ayudar a construir sitios y componentes accesibles siguiendo las pautas de accesibilidad para el contenido web y mejores prácticas.
HTML semántico
Esta es la base para construir componentes accesibles. Utilizar un elemento de acuerdo a su propósito y no su presentación visual debería ser el punto de partida, ya que en la mayoría de los casos viene con funcionalidades de accesibilidad integradas por defecto.
Encabezados
Los encabezados describen secciones de la página y ayudan a transmitir una jerarquía visual. Si están estructurados correctamente permiten a los usuarios comprender mejor cómo se organiza el contenido de ella.
Son especialmente importantes para las personas que utilizan un lector de pantalla, puesto que les permiten navegar por la página utilizándolos como atajos, dándoles acceso más fácil a la información y sirviendo como una tabla de contenidos.
No te saltes niveles de encabezados
Seguir una jerarquía de encabezados lógica es considerado una buena práctica. Ayuda a las personas ciegas a comprender la importancia relativa de diferentes secciones. Se recomienda utilizar solo un <h1>
que describa el propósito de la página, idealmente este encabezado de nivel 1 es igual al título. Un <h2>
debe ser usado para describir una sección nueva. Si existe una subsección dentro del <h2>
debería ser un <h3>
, y así sucesivamente hasta un <h6>
en caso de ser necesario.
1. <h1>Escarabajos</h1>
1. <h2>Etimología</h2>
2. <h2>Distribución y diversidad</h2>
1. <h3>Periodo Paleozoico Tardío</h3>
2. <h3>Jurásico</h3>
3. <h2>Morfología externa</h2>
1. <h3>Cabeza</h3>
2. <h3>Tórax</h3>
1. <h4>Protórax</h4>
3. <h3>Patas</h3>
<h1>Mascotas favoritas</h1>
<h2>Perros</h2>
<h3>Comida de perro</h3>
<h2>Gatos</h2>
Puedes instalar y usar la extensión Headingsmap para verificar que exista solo una instancia de <h1>
y que no se salten niveles de encabezados.
Usa código semántico
Es importante que los encabezados utilicen código semántico. Los elementos desde <h1>
a <h6>
tienen un rol implícito que ayuda a describir lo que son. Por ejemplo, un <h1>
tiene un rol implícito de encabezado y un nivel que indica su jerarquía. Esta información será expuesta por el API de accesibilidad y permite que tecnología de asistencia como un lector de pantalla lo identifique como un encabezado de nivel 1.
Este es uno de los motivos por los que es importante usar un elemento de acuerdo a su propósito. Un div
con la presentación visual de un encabezado tendría un tamaño de tipografía más grande y estaría en negrita. Su significado se puede inferir por estas pistas visuales. Sin embargo, una persona ciega usando un lector de pantalla no puede hacer esa asociación, porque un div
no tiene ningún significado semántico y por tanto tecnologías de asistencia no tienen cómo identificar al elemento como un encabezado.
No uses código semántico si no es un encabezado
Así como es importante utilizar código semántico cuando describes una sección dentro de una página, es igual de importante no usarlo cuando solo estás intentando lograr cierta presentación visual. Por ejemplo:
<p>Puedes encontrarnos en la siguiente dirección</p>
<h4>Avenida calle 123</h4>
La dirección está usando un encabezado de nivel 4 para cambiar su apariencia visual. Sin embargo, esto no representa una sección en la página, y por tanto no debería ser un encabezado.
Encabezados describen el contenido debajo de ellos
Cuando hay contenido relacionado con un encabezado pero se ubica por encima de él en el DOM, una persona ciega que utiliza un lector de pantalla para navegar por la página usando los encabezados como acceso rápido se saltará ese contenido. Un ejemplo común de esto es cuando se coloca información meta, como la fecha o el autor en una publicación de un blog:
<!-- No recomendado -->
<div>
<time>25 de febrero, 2023</time>
<h2><a href="...">Introducción a Windows con alto contraste</a></h2>
</div>
<!-- Recomendado -->
<div>
<h2><a href="...">Introducción a Windows con alto contraste</a></h2>
<time>25 de febrero, 2023</time>
</div>
Puntos de referencia
HTML5 introdujo elementos de seccionamiento para ayudar a dar estructura a las páginas y sirven como puntos de referencia. Estos son especialmente importantes para usuarios de lectores de pantalla puesto que funcionan como atajos para acceder a distintas partes importantes del sitio, o para evitar otras secciones redundantes como la navegación principal.
La mayoría de los puntos de referencia, también conocidos como landmarks en inglés tienen un elemento nativo en HTML, pero además es posible utilizar roles ARIA. De hecho, uno de los landmark solo es expuesto usando un rol explícito ARIA. Los puntos de referencia disponibles son los siguientes.
Banner
Este punto de referencia puede contener principalmente contenido orientado al sitio, en lugar de contenido específico de la página.
El contenido orientado al sitio suele ser el logo o la navegación principal. El elemento nativo del rol banner
es <header>
y la recomendación es utilizar solo uno en la página.
Complementary
Este punto de referencia existe para ser usado como contenido complementario al contenido principal. El elemento nativo del rol complementary
es <aside>
y un ejemplo de uso podría ser una barra lateral con artículos relacionados al artículo principal.
Contentinfo
Este punto de referencia puede contener información sobre el documento padre. Usualmente es usuado al final del sitio con información global del sitio y enlaces. El elemento nativo del rol contentinfo
es footer
y la recomendación al igual que el landmark <header
es utilizar solo uno en la página.
<footer role="contentinfo">
</footer>
Usar ambos resulta redundante, pero el motivo es que Voiceover hasta la versión 13 de Safari no exponía el rol implícito. Es recomendable revisar desde qué navegador y versión tienes visitas y en base a eso tomar una decisión.
Form
Este punto de referencia puede ser utilizado como contenedor de elementos de un formulario. Puede resultar útil como landmark si cuentas con más de un formulario en la página, pero es necesario darle un nombre accesible para que sea identificado como un punto de referencia.
Si el formulario cuenta con un encabezado puedes darle un nombre accesible utilizando aria-labelledby
:
<h2 id="contacto">Contacto</h2>
<form aria-labelledby="contacto">
...
</form>
Main
Este punto de referencia debe contener el contenido principal de la página.
El elemento nativo del rol main
es <main>
y en la página solo debiese existir una instancia.
Navigation
Este punto de referencia debe contener enlaces para navegar dentro del sitio. Es posible y recomendable utilizar todos los que sean necesarios dependiendo del contenido del sitio. Ejemplos de landmarks de navegación son la navegación principal, de migas de pan o paginación.
Region
Este punto de referencia puede ser utilizado cuando tengas contenido que deba ir en un landmark y no exista ningún otro que lo describa correctamente. Es posible utilizar el elemento nativo <section>
pero este no será expuesto como un punto de referencia a menos que le des un nombre accesible.
Search
Este punto de referencia puede contener controles relacionados con búsquedas dentro del sitio. No cuenta con un elemento nativo pero puede ser usado en un elemento form <form role="search">
o de manera explícita solo con el rol en un <div role="search">
.
Todo el contenido debe estar dentro de un punto de referencia
Es considerado una buena práctica que todo el contenido de una página esté dentro de un punto de referencia. Esto permite que sea más fácil acceder al contenido para personas que usan tecnología de asistencia.
Múltiples puntos de referencia del mismo tipo
Si existen muchos puntos de referencia del mismo tipo, los usuarios tendrán que pasar por demasiada información adicional para encontrar lo que están buscando. Un punto de referencia es una sección importante de la página. Y cuando hay un exceso de puntos de referencia estamos indicando que todo es importante. Y si todo es importante, nada lo es realmente. Por eso es recomendable no abusar de su uso.
En caso de que tengas múltiples puntos de referencia del mismo tipo la recomendación es darle un nombre accesible único. Esto permite que sea posible distinguirlos. A continuación hay un ejemplo utilizando aria-label
para darle un nombre accesible a landmarks de navegación:
<nav aria-label="principal">
...
</nav>
<nav aria-label="paginación">
...
</nav>
Botones y enlaces
¿Deberías usar un enlace o un botón? Usar el elemento correcto es importante porque un lector de pantalla lo anunciará como tal, y los usuarios esperan cierta funcionalidad según el elemento. Los enlaces pueden ser activados usando la tecla Enter y llevan al usuario a una página diferente o a una sección dentro de la página. Los botones pueden ser activados con las teclas Enter y espacio y sirven para gatillar acciones, por ejemplo, abrir un menú. Utiliza el elemento correcto según la interacción para crear una experiencia más predecible. Los enlaces deben utilizarse como elementos de navegación y los botones para activar una acción.
Determina el propósito solo con la etiqueta
Entender el propósito de los enlaces en base a su etiqueta ayuda a los usuarios a decidir si quieren seguirlo o no. Ser capaz de identificar el propósito de un enlace cuando está fuera de contexto también ayuda a los usuarios de lectores de pantalla que tienen la opción de abrir una lista de enlaces en una página.
Asegúrate de que el texto sea descriptivo y que el propósito pueda entenderse cuando se coloca fuera de contexto.
Lo mismo es recomendable para los botones con ícono y sin etiqueta. Estos deben tener texto descriptivo visualmente oculto. Evita incluir la palabra "botón" como parte del nombre accesible ya que un lector de pantalla ya lo anunciará como tal, por lo que será redundante.
Si hay varios botones de iconos del mismo tipo, asegúrate de que tengan un nombre accesible único que los distinga.
<button>
<span class="visualmente-escondido">Editar título</span>
<icon> // pseudocódigo
</button>
<button>
<span class="visualmente-escondido">Editar descripción</span>
<icon> // pseudocódigo
</button>
Teclado y Foco
Todos los elementos interactivos deben tener un foco claramente visible para que las personas que dependen del uso de un teclado sepan qué elemento se encuentra enfocado. Al crear un componente, usa la tecla Tab para comprobar que los elementos interactivos reciben foco.
Foco en elementos no interactivos
Como regla general los elementos que no son interactivos no deben recibir foco. Un error bastante común es pensar que agregar tabindex="0"
a todos los elementos ayuda en la navegación del sitio con el teclado. Sin embargo, esto resulta problemático y debe ser evitado.
- Las tecnologías de asistencia como un lector de pantalla ya cuentan con opciones para acceder de manera rápida a distintas secciones y elementos de la página.
- Dificulta la navegación para personas que usan un teclado sin un lector de pantalla dado que tienen que activar la tecla tab una gran cantidad de veces para poder llegar a los elementos interactivos.
Excepciones de foco en elementos no interactivos
Algunos widget interactivos como un diálogo requieren de un manejo del foco. Para este patrón de diseño en particular es muy común manejar el foco de la siguiente manera:
- El usuario activa el botón que abre el diálogo.
- El foco se mueve al contenedor del diálogo que tiene un atributo
tabindex="-1"
permitiendo que el usuario continúe interactuando con el contenido de adentro. - El foco es atrapado dentro del diálogo impidiendo que el usuario salga de este.
- Activar la tecla Esc cierra el diálogo y el foco regresa al botón que lo activó para que el usuario pueda continuar desde donde estaba inicialmente.
En estos casos es común utilizar tabindex="-1"
que permite mover el foco de un elemento a otro utilizando Javascript pero no será parte de la secuencia de tab. Es decir, no recibirá foco cuando el usuario active la tecla tab. Como regla general no debes mover el foco de un elemento a otro a menos que el usuario haya gatillado una acción, por ejemplo al activar un botón. Puedes encontrar ejemplos de widgets interactivos en la sección de patrones de diseño del sitio aria practices.
Otra excepción común son los contenedores con desplazamiento horizontal o vertical. Un ejemplo de esto es al crear una tabla responsiva con desplazamiento horizontal. En ese caso puedes usar un contenedor con el atributo tabindex="0"
para que sea enfocable y así el desplazamiento horizontal sea accesible usando el teclado. Sin embargo, si algo recibe foco también debe tener un rol y un nombre accesible para que el usuario sepa con que está interactuando. En este caso es posible utilizar como rol un landmark genérico como role="region"
o el elemento nativo <section>
.
Orden de foco
Un lector de pantalla anunciará el contenido según el orden en que los elementos están en el DOM. Si el foco salta a lugares diferentes de manera inesperada, puede desorientar a algunos usuarios.
Ten cuidado al cambiar el orden de elementos con flexbox y css grid, y asegúrate de que el orden visual coincida con el orden del DOM de manera lógica.
Es posible que el orden del foco no siempre sea idéntico al orden de lectura siempre y cuando se preserve el significado y el usuario todavía pueda comprender el contenido de la página. En otras palabras, puede haber más de un orden que preserve el significado del contenido. Por ejemplo, si creas una página con una barra lateral que está visualmente en el lado izquierdo, pero en el DOM se posiciona después del área de contenido principal (en el lado derecho). El orden visual no va a coincidir con el DOM, pero esto no afecta el significado.
Puedes probar el orden del foco usando la tecla Tab. Comienza desde el principio de la página y comprueba que los elementos interactivos reciban el foco en un orden secuencial que coincida con el orden de lectura visual de la página o componente.
Etiquetas
Todos los controles de formulario deben tener una etiqueta visible, asociada programáticamente y en proximidad cercana a su control.
Etiquetas visibles
Los controles de formulario deben tener una etiqueta visible para que todos puedan entender su propósito. Esto incluye campos de entrada, casillas de verificación, radio botones, listas desplegables y widgets interactivos como un combobox.
Excepciones
En algunos casos, un ícono puede servir como etiqueta visual. Un ejemplo de esto es un ícono de lupa. Dado que el significado del ícono es entendible universalmente, no se necesita una etiqueta visual.
Asociación programática
Para que la relación entre etiqueta y control pueda ser entendida por todas las personas es necesario asociarlas programáticamente.
Existen diferentes métodos para hacer esto, y el correcto va a depender del control y el caso en particular.
Asociación explícita
La asociación explícita se hace a través del atributo for
. El valor del atributo debe ser igual al valor del atributo id en el control y debe ser único en la página. Si existen valores repetidos pueden generar un conflicto y las etiquetas podrían quedar asociadas al control incorrecto.
<label for="foo">Nombre<label>
<input type="text" id="foo" />
Asociación implícita
La asociación implícita se hace envolviendo el elemento label sobre el control.
<label>
Nombre
<input type="text" />
</label>
Asociación grupal
Usa <fieldset>
y <legend>
para asociar un grupo de controles relacionados. Ten en cuenta que <legend>
debe ser el primer hijo dentro del elemento <fieldset>
.
<fieldset>
<legend>Frutas preferidas</legend>
<input type="checkbox" id="manzana" /> <label for="manzana">Manzana</label>
<input type="checkbox" id="pera" /> <label for="pera">Pera</label>
<input type="checkbox" id="Sandia" /> <label for="sandia">Sandía</label>
</fieldset>
Si por algún motivo no puedes usar <fieldset>
puedes utilizar la alternativa de agrupación con ARIA
<div role="group" aria-labelledby="foo">
<div id="foo">Frutas preferidas</div>
<input type="checkbox" id="manzana" /> <label for="manzana">Manzana</label>
<input type="checkbox" id="pera" /> <label for="pera">Pera</label>
<input type="checkbox" id="Sandia" /> <label for="sandia">Sandía</label>
</div>
Instrucciones
Si un control requiere de instrucciones, por ejemplo para especificar un límite de caracteres puedes usar aria-describedby
para crear esa asociación programática.
<label for="foo">Comentario</label>
<textarea id="foo" aria-describedby="bar"></textarea>
<p id="bar">Límite de 200 caracteres</p>
Proximidad
Cuando las etiquetas de los campos de formulario se colocan donde el usuario las espera visualmente, es más fácil entender formularios complejos y encontrar campos específicos. Si una etiqueta está visualmente adyacente a su control correspondiente también permitirá a los usuarios de magnificadores de pantalla encontrarla fácilmente.
Zoom
Los elementos con posición fixed
o sticky
siempre se mantendrán visibles en el navegador mientras el resto del contenido desaparece por debajo. Esto puede ser un problema para personas con baja visión que dependen del zoom para leer texto. Cuando se hace zoom en una página con secciones sticky, estas podrían cubrir grandes partes de la página, dejando solo una pequeña parte de la pantalla para mostrar el contenido.
Asegúrate de desactivar la posición utilizando una media query.
@media (max-height: 500px) {
header {
position: static;
}
}