En el Módulo 3 cerramos el recorrido por los cuatro principios POUR entendidos como requisitos: aprendimos qué exige WCAG en cada criterio. Ahora empieza la parte de implementación: cómo se cumple con HTML y CSS reales. Y arrancamos por donde debe arrancarse, porque es la vía más directa, barata y robusta de conseguir accesibilidad: el HTML semántico. Cuando eliges el elemento correcto, el navegador te regala name, role y value (los tres pilares del criterio 4.1.2 que vimos como requisito) sin que escribas ni una línea de JavaScript ni de ARIA. Un <button> ya "es" un botón para el lector de pantalla de Marta; un <nav> ya "es" una región de navegación por la que Lucía puede saltar. La semántica es, literalmente, accesibilidad gratis.
En esta lección construimos el esqueleto de la landing y el catálogo de Cursalia usando los elementos adecuados y veremos, contrastando marcado bueno frente a malo, por qué la etiqueta que eliges cambia por completo la experiencia de las tecnologías de asistencia.
Contenido
- Por qué la semántica da name/role/value gratis
- Landmarks: las regiones de la página
- Jerarquía de encabezados h1–h6
- Listas: ul, ol y dl
- Tablas de datos accesibles
- Botón vs enlace: el error del
<div onclick> - Controles de formulario nativos (mención)
- Atributos de página:
lang,titley el skip link - La landing de Cursalia: buena vs mala estructura
- Errores comunes y consejos
- Ejercicios
- Conclusión
Por qué la semántica da name/role/value gratis
Recordemos del Módulo 2 el árbol de accesibilidad: el navegador construye, a partir de tu HTML, una versión paralela del DOM que expone a las tecnologías de asistencia el rol (qué es el elemento), el nombre (cómo se llama) y el valor/estado (en qué situación está) de cada componente. El HTML semántico es la forma en que "rellenas" ese árbol sin esfuerzo.
| Si escribes... | El árbol de accesibilidad expone... | Coste extra |
|---|---|---|
<button>Matricular</button> |
role=button, name="Matricular", focusable, activable con Enter/Espacio | Cero |
<nav> |
role=navigation (landmark saltable) | Cero |
<h2>Cursos destacados</h2> |
role=heading, level=2 (aparece en la lista de encabezados) | Cero |
<div class="button" onclick> |
role=generic (¡nada!), no focusable, no activable con teclado | Mucho: hay que recrearlo todo con ARIA + JS |
La conclusión práctica es la regla de oro de la accesibilidad, que repetiremos en 04-03:
Usa el elemento HTML nativo que ya tiene la semántica que necesitas antes de recrearla con ARIA y JavaScript.
Landmarks: las regiones de la página
Los landmarks (puntos de referencia) son regiones de alto nivel que permiten a un usuario de lector de pantalla como Marta saltar directamente a "el contenido principal" o "la navegación" sin escuchar toda la página. NVDA ofrece una tecla para listar landmarks igual que ofrece una para listar encabezados.
Los elementos de sección de HTML se mapean automáticamente a roles landmark:
| Elemento HTML | Rol de accesibilidad (landmark) | Uso |
|---|---|---|
<header> (a nivel de página) |
banner | Cabecera del sitio: logo, buscador |
<nav> |
navigation | Menús de navegación |
<main> |
main | Contenido principal (uno por página) |
<aside> |
complementary | Contenido relacionado: filtros, promos |
<footer> (a nivel de página) |
contentinfo | Pie: avisos legales, contacto |
<section> con nombre |
region | Sección temática con encabezado |
<article> |
article | Contenido autónomo (una tarjeta de curso) |
Ejemplo correcto del armazón de una página de Cursalia:
<body>
<header>
<a href="/"><img src="logo-cursalia.svg" alt="Cursalia"></a>
<nav aria-label="Principal">
<ul>
<li><a href="/cursos">Catálogo</a></li>
<li><a href="/precios">Precios</a></li>
</ul>
</nav>
</header>
<main>
<h1>Aprende a tu ritmo con Cursalia</h1>
<!-- contenido principal -->
</main>
<aside aria-label="Filtros del catálogo">
<!-- filtros -->
</aside>
<footer>
<p>© 2026 Cursalia</p>
</footer>
</body>Frente al mismo armazón hecho solo con <div>, que no aporta ninguna región:
<!-- INCORRECTO: para el lector de pantalla esto es una sopa de "grupos genéricos" -->
<div class="header">...</div>
<div class="nav">...</div>
<div class="content">...</div>
<div class="footer">...</div>Para el lector de Marta, la primera versión tiene cinco regiones nombradas por las que navegar; la segunda no tiene ninguna. Fíjate en el detalle de aria-label="Principal" en el <nav>: si hay más de un landmark del mismo tipo (por ejemplo, la navegación principal y otra en el pie), hay que distinguirlos con un nombre. Esto es semántica reforzada, no reemplazada; lo veremos bien en 04-03.
graph TD
A[body] --> B["header → banner"]
A --> C["nav → navigation"]
A --> D["main → main"]
A --> E["aside → complementary"]
A --> F["footer → contentinfo"]
D --> G["article → article (tarjeta de curso)"]
Jerarquía de encabezados h1–h6
Los encabezados no son "texto grande": son el índice navegable de la página. Marta pulsa la tecla H en NVDA para saltar de encabezado en encabezado y hacerse un mapa mental del contenido, y puede pulsar 2 para ir solo a los de nivel 2. Por eso la jerarquía debe reflejar la estructura lógica, no el tamaño visual (el tamaño es cosa del CSS).
Reglas del criterio 1.3.1 aplicadas a encabezados:
- Un solo
<h1>por página, que describe el tema global. - No saltar niveles al bajar: tras un
<h2>no se pasa directamente a un<h4>. - El nivel indica jerarquía, no apariencia. Si quieres un h2 pequeño, lo achicas con CSS.
<!-- CORRECTO: jerarquía sin saltos -->
<main>
<h1>Catálogo de cursos</h1>
<h2>Cursos destacados</h2>
<h3>Accesibilidad Web</h3>
<h3>JavaScript moderno</h3>
<h2>Novedades</h2>
<h3>Diseño de sistemas</h3>
</main><!-- INCORRECTO: se elige el encabezado por su tamaño, no por jerarquía -->
<h1>Catálogo de cursos</h1>
<h4>Cursos destacados</h4> <!-- salta de 1 a 4 -->
<h2>Accesibilidad Web</h2> <!-- y ahora sube a 2: el índice queda incoherente -->Listas: ul, ol y dl
Cuando marcas un conjunto de elementos como lista, el lector de pantalla anuncia "lista de 8 elementos" y permite navegar por ella. Ese contexto se pierde si usas <div> o <br>. Tres tipos:
<ul>: lista no ordenada (el orden no importa). El menú de navegación, las tarjetas del catálogo.<ol>: lista ordenada (la secuencia importa). Los pasos de matriculación.<dl>: lista de definiciones (pares término/descripción). La ficha de un curso: "Duración: 20 h", "Nivel: Intermedio".
<!-- CORRECTO: la parrilla de cursos ES una lista -->
<ul class="grid-cursos">
<li><article>...tarjeta Accesibilidad Web...</article></li>
<li><article>...tarjeta JavaScript moderno...</article></li>
</ul>
<!-- CORRECTO: metadatos del curso como lista de definiciones -->
<dl>
<dt>Duración</dt><dd>20 horas</dd>
<dt>Nivel</dt><dd>Intermedio</dd>
<dt>Idioma</dt><dd>Español</dd>
</dl><!-- INCORRECTO: parece una lista, pero no lo es para nadie más que para el ojo -->
<div class="grid-cursos">
<div>...tarjeta...</div>
<div>...tarjeta...</div>
</div>Tablas de datos accesibles
Una tabla de datos (no de maquetación, que no se debe usar) necesita marcado semántico para que Marta entienda a qué fila y columna pertenece cada celda. Las piezas clave:
<caption>: título de la tabla, lo primero que anuncia el lector.<th>para las celdas de encabezado, frente a<td>para datos.scope="col"/scope="row": indica si el encabezado gobierna una columna o una fila.<thead>/<tbody>: separan encabezados del cuerpo.
<!-- CORRECTO: comparativa de planes de Cursalia -->
<table>
<caption>Comparativa de planes de suscripción</caption>
<thead>
<tr>
<th scope="col">Plan</th>
<th scope="col">Precio/mes</th>
<th scope="col">Cursos incluidos</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Básico</th>
<td>9 €</td>
<td>50</td>
</tr>
<tr>
<th scope="row">Pro</th>
<td>19 €</td>
<td>Todos</td>
</tr>
</tbody>
</table>Sin scope ni <th>, el lector recita una retahíla de números sin decir "Pro, Precio/mes, 19 €": la relación entre dato y encabezado se pierde.
Botón vs enlace: el error del <div onclick>
Es una de las decisiones más equivocadas en la práctica. La regla es sencilla:
<a href>(enlace): navega a otro lugar (otra página, otra sección, un ancla). Rol link. El lector lo anuncia como "enlace".<button>: ejecuta una acción en la página actual (abrir el filtro, enviar el formulario, reproducir el vídeo). Rol button. El lector lo anuncia como "botón".
Ambos son focusables y operables con teclado de fábrica: el enlace se activa con Enter, el botón con Enter y Espacio. Un <div> o un <span> con onclick no tiene ninguna de esas propiedades.
<!-- CORRECTO -->
<a href="/cursos/accesibilidad-web">Ver curso de Accesibilidad Web</a>
<button type="button">Abrir filtros</button><!-- INCORRECTO: la trampa clásica -->
<div class="btn" onclick="abrirFiltros()">Abrir filtros</div>Ese <div> falla en cascada para Lucía (que solo usa teclado) y Marta:
- No recibe foco: Lucía no puede tabular hasta él (habría que forzar
tabindex="0"). - No se activa con teclado: aunque tuviera foco, Enter/Espacio no hacen nada (habría que escuchar teclas con JS).
- No tiene rol: Marta no oye "botón", oye texto suelto, y no sabe que es pulsable.
Para "arreglar" ese <div> hay que añadir role="button", tabindex="0", manejadores de teclado para Enter y Espacio... es decir, reimplementar a mano todo lo que <button> te da gratis. Recrear controles nativos con ARIA es precisamente lo que veremos en el Módulo 5; aquí la lección es evitar tener que hacerlo.
Controles de formulario nativos (mención)
Los elementos <input>, <select>, <textarea> y <button> traen de serie rol, foco, operación por teclado y estados. Un <input type="checkbox"> ya comunica marcado/desmarcado; un <select> ya es una lista desplegable operable. Aprovecharlos es la base de un formulario accesible, pero su asociación con etiquetas, la agrupación y los errores son el tema completo de la lección 04-02, así que aquí solo dejamos constancia de que existen y de que son la primera opción.
Atributos de página: lang, title y el skip link
Tres detalles del documento con gran impacto:
langen<html>: le dice al lector qué idioma pronunciar. Sin él, NVDA podría leer el español de Cursalia con fonética inglesa y volverse ininteligible. Si dentro hay un fragmento en otro idioma, se marca conlangen ese elemento (esto conecta con la i18n del Módulo 7).<title>de la página: es lo primero que anuncia el lector al cargar y lo que se ve en la pestaña. Debe ser único y descriptivo:Catálogo de cursos · Cursalia.- Skip link ("saltar al contenido"): un enlace que es el primer foco de la página y lleva directamente al
<main>, para que Lucía no tenga que tabular por toda la cabecera y el menú en cada página.
<!doctype html>
<html lang="es">
<head>
<title>Catálogo de cursos · Cursalia</title>
</head>
<body>
<!-- Primer elemento focusable de la página -->
<a class="skip-link" href="#contenido">Saltar al contenido principal</a>
<header>...</header>
<main id="contenido" tabindex="-1">
<h1>Catálogo de cursos</h1>
</main>
</body>
</html>El skip link suele ocultarse visualmente y mostrarse solo al recibir foco, para no molestar al usuario de ratón pero seguir estando disponible para el teclado:
.skip-link {
position: absolute;
left: -9999px; /* fuera de pantalla, pero presente en el DOM */
}
.skip-link:focus {
left: 1rem;
top: 1rem; /* aparece al tabular hacia él */
}La landing de Cursalia: buena vs mala estructura
Juntemos todo comparando dos versiones de la misma landing.
<!-- INCORRECTO: todo divs, sin encabezados reales, "botones" que son divs -->
<div class="top">
<div class="menu"><span onclick="ir('/cursos')">Catálogo</span></div>
</div>
<div class="hero">
<div class="big">Aprende a tu ritmo</div>
<div class="btn" onclick="registro()">Empieza gratis</div>
</div>
<div class="cards">
<div class="card">Accesibilidad Web</div>
<div class="card">JavaScript moderno</div>
</div>Para Marta esto es una página sin regiones, sin encabezados y sin controles: imposible de recorrer con eficiencia. Para Lucía, nada recibe foco.
<!-- CORRECTO: mismo diseño visual, semántica completa -->
<a class="skip-link" href="#contenido">Saltar al contenido</a>
<header>
<nav aria-label="Principal">
<ul><li><a href="/cursos">Catálogo</a></li></ul>
</nav>
</header>
<main id="contenido">
<h1>Aprende a tu ritmo con Cursalia</h1>
<p><a class="cta" href="/registro">Empieza gratis</a></p>
<section aria-labelledby="destacados">
<h2 id="destacados">Cursos destacados</h2>
<ul class="cards">
<li>
<article>
<h3><a href="/cursos/accesibilidad-web">Accesibilidad Web</a></h3>
<img src="acc.webp" alt=""> <!-- alt en 05-04 -->
</article>
</li>
<li>
<article>
<h3><a href="/cursos/javascript">JavaScript moderno</a></h3>
</article>
</li>
</ul>
</section>
</main>El CSS puede hacer que ambas se vean idénticas. La diferencia no está en los píxeles, sino en la información estructural que una expone y la otra destruye. (El alt="" de la imagen tiene sentido y lo estudiaremos a fondo en 05-04.)
Errores Comunes y Consejos
- Usar
<div>/<span>para todo. Es el error raíz. Antes de escribir un<div>, pregúntate si existe un elemento que signifique lo que quieres decir. - Elegir el encabezado por su tamaño. Un
<h3>no es "un título mediano"; es un nivel jerárquico. Ajusta el tamaño con CSS, nunca cambiando el nivel. - Varios
<h1>o saltos de nivel. Rompen el índice de la página. Un<h1>, y baja de uno en uno. - Botón que navega o enlace que actúa. Si cambia de URL, es
<a>; si actúa en la página, es<button>. No los intercambies por estética. - Tablas para maquetar. Reserva
<table>para datos tabulares; usa CSS Grid/Flexbox para la disposición visual. - Olvidar
langy<title>. Son una línea cada uno y mejoran drásticamente la lectura por voz. - Consejo: navega tu propia página solo con el teclado (Tab, Enter) y con la lista de encabezados de un lector. Si no puedes recorrerla, tu semántica está incompleta.
Ejercicios
Ejercicio 1: convertir un div-soup en landmarks
Tienes este fragmento de la cabecera de Cursalia. Reescríbelo con los elementos semánticos correctos, añadiendo el nombre necesario al nav.
<div class="header">
<div class="logo">Cursalia</div>
<div class="menu">
<div class="item"><span onclick="ir('/cursos')">Catálogo</span></div>
<div class="item"><span onclick="ir('/precios')">Precios</span></div>
</div>
</div>Ejercicio 2: arreglar la jerarquía de encabezados
Un maquetador eligió los encabezados por su tamaño visual. Corrige los niveles para que la jerarquía sea lógica y sin saltos.
<h1>Ficha del curso: Accesibilidad Web</h1>
<h3>Descripción</h3>
<h2>Temario</h2>
<h4>Módulo 4: HTML y CSS</h4>Ejercicio 3: botón o enlace
Para cada control, decide si debe ser <a href> o <button> y escríbelo:
- "Ir a la página de precios".
- "Reproducir la vídeo-lección" (en la misma página).
- "Descargar el certificado" (navega a un PDF).
- "Abrir el desplegable de filtros del catálogo".
Soluciones
Solución 1. Cada <div> estructural se sustituye por su equivalente semántico; los "items" pulsables que navegan son enlaces reales:
<header>
<a href="/" class="logo">Cursalia</a>
<nav aria-label="Principal">
<ul>
<li><a href="/cursos">Catálogo</a></li>
<li><a href="/precios">Precios</a></li>
</ul>
</nav>
</header>El aria-label="Principal" es necesario porque probablemente haya otro <nav> (por ejemplo en el pie) y hay que distinguirlos.
Solución 2. Debe bajar de forma progresiva sin saltos y con un solo <h1>:
<h1>Ficha del curso: Accesibilidad Web</h1>
<h2>Descripción</h2>
<h2>Temario</h2>
<h3>Módulo 4: HTML y CSS</h3>"Descripción" y "Temario" son secciones hermanas de primer nivel (h2); el módulo cuelga del temario (h3).
Solución 3.
- Enlace: navega a otra página →
<a href="/precios">Ir a precios</a>. - Botón: acciona algo en la misma página →
<button type="button">Reproducir</button>. - Enlace: lleva a un recurso (el PDF) →
<a href="/certificado.pdf" download>Descargar certificado</a>. - Botón: abre/cierra un panel en la página →
<button type="button" aria-expanded="false">Filtros</button>. Elaria-expandedy su comportamiento los veremos en 04-03 y 05-01.
Conclusión
El HTML semántico es el cimiento de todo lo demás en accesibilidad: eligiendo bien los elementos obtienes name, role y value gratis, regiones navegables, un índice de encabezados y controles operables con teclado, sin escribir código adicional. Hemos maquetado la landing de Cursalia con landmarks, jerarquía de encabezados, listas, tablas de datos, enlaces y botones correctos, y hemos fijado la regla de oro: usa el elemento nativo antes que recrearlo con ARIA (idea que ampliaremos en 04-03).
El siguiente paso natural es el punto donde la semántica se vuelve más delicada y donde se concentran los criterios 3.3.x: los formularios. En la lección 04-02 · Formularios Accesibles aplicaremos todo esto al formulario de matrícula de Cursalia —etiquetas, agrupaciones, campos obligatorios, ayudas y mensajes de error— para que Marta, Hugo y Lucía puedan completarlo sin barreras.
Curso de Accesibilidad Web
Módulo 1: Introducción a la Accesibilidad Web
- ¿Qué es la Accesibilidad Web?
- Importancia de la Accesibilidad Web
- Visión General de las Leyes y Normas de Accesibilidad
- Introducción a WCAG
Módulo 2: Comprensión de Discapacidades y Tecnologías de Asistencia
- Tipos de Discapacidades
- Visión General de Tecnologías de Asistencia
- Cómo las Personas con Discapacidades Usan la Web
Módulo 3: Principios del Diseño Accesible
- Perceptible: Hacer el Contenido Disponible a los Sentidos
- Operable: Interfaz de Usuario y Navegación
- Comprensible: Información y Operación
- Robusto: Compatibilidad con Tecnologías Actuales y Futuras
Módulo 4: Implementación de Accesibilidad en HTML y CSS
- HTML Semántico
- Formularios Accesibles
- Uso de Roles y Propiedades ARIA
- Contraste de Color y Redimensionamiento de Texto
Módulo 5: Accesibilidad en JavaScript y Multimedia
- Creación de Widgets de JavaScript Accesibles
- Accesibilidad del Teclado
- Contenido de Video y Audio Accesible
- Proporcionar Alternativas de Texto para Imágenes
Módulo 6: Pruebas y Evaluación de la Accesibilidad
- Técnicas de Pruebas Manuales
- Herramientas de Pruebas Automatizadas
- Pruebas de Usuario con Tecnologías de Asistencia
- Interpretación de Informes de Accesibilidad
