Accesibilidad WCAG: páginas para todas las personas
La accesibilidad web (a11y) garantiza que cualquiera pueda usar tu página: personas ciegas que navegan con lector de pantalla, usuarios que solo usan el teclado, gente con baja visión, dislexia o movilidad reducida. La referencia mundial son las pautas WCAG del W3C, con tres niveles de conformidad: A (mínimo), AA (estándar legal en la UE y objetivo de este curso) y AAA (excelencia).
1.1 · La regla de oro: semántica primero, ARIA después
La primera norma de ARIA dice literalmente: «no uses ARIA si existe un elemento HTML
nativo que haga el trabajo». Un <button> ya es enfocable, ya responde a
Espacio/Enter y ya anuncia su rol. Si lo sustituyes por un
<div onclick>, tendrás que reconstruir a mano todo eso con ARIA… y casi
siempre lo harás peor.
<div class="menu-btn" onclick="abrir()">☰</div>
<button class="menu-btn"
aria-expanded="false"
aria-controls="menu-principal">
☰ <span class="sr-only">Abrir menú</span>
</button>
Cuando la semántica nativa no llega (un componente tipo acordeón, una pestaña,
un interruptor), ahí sí entran los roles (role="tab",
role="switch", role="alert"…) y los estados ARIA:
| Atributo | Para qué sirve | Ejemplo |
|---|---|---|
aria-label | Nombra un elemento sin texto visible | Icono de búsqueda: aria-label="Buscar" |
aria-labelledby | Nombra usando el texto de OTRO elemento (por id) | Diálogo nombrado por su título |
aria-describedby | Vincula una descripción/ayuda adicional | Pista bajo un campo de contraseña |
aria-expanded | Indica si un desplegable está abierto | Menú hamburguesa, acordeón |
aria-controls | Apunta al id del contenido que gestiona | El botón y su menú |
aria-current | Marca el elemento activo de una lista | Página actual del menú |
aria-hidden | Oculta a tecnologías de asistencia | Iconos decorativos repetidos |
aria-live | Anuncia cambios dinámicos | «Añadido al carrito» |
role="alert" implica aria-live="assertive": el lector
de pantalla interrumpe y lee el mensaje nada más aparecer. Perfecto para errores de formulario.1.2 · Navegación por teclado
Muchos usuarios no tocan el ratón nunca. Tu página debe poder recorrerse completa con Tab / Shift+Tab, en un orden lógico y viendo siempre dónde está el foco. Las reglas:
tabindex="0": añade un elemento al orden natural de tabulación (usar con moderación).tabindex="-1": enfocable solo mediante JavaScript (element.focus()).tabindex="1" o superior: rompe el orden esperado. Prohibido en este curso.outline: none sin dar alternativa visible.El gesto más profesional de accesibilidad es el enlace de salto («skip link»): invisible hasta que recibe foco, permite saltarse el menú y llegar directo al contenido:
<body>
<a class="skip-link" href="#contenido">Saltar al contenido</a>
<header>…menú largo con 20 enlaces…</header>
<main id="contenido" tabindex="-1">…</main>
</body>
<!-- CSS mínimo del truco -->
.skip-link { position:absolute; top:-100%; left:1rem; }
.skip-link:focus { top:1rem; }
SEO on-page: hazte entender por Google
El SEO on-page es todo lo que puedes controlar dentro de tu HTML para que
los buscadores entiendan, posicionen y presenten bien tus páginas. Empieza por el
<head>: es la ficha técnica que lee Google antes que nadie.
<title> único por página, 50–60 caracteres, palabra clave al inicio.<meta name="description"> única, 140–160 caracteres, con gancho comercial.<h1> que resuma el tema; jerarquía h2/h3 sin saltos.2.1 · El head de un profesional
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Curso de HTML desde cero | Técnico Web</title>
<meta name="description"
content="Aprende HTML de cero a nivel profesional con ejercicios guiados,
exámenes y proyectos reales. Gratis y en español.">
<link rel="canonical" href="https://tecnicoweb.es/curso-html">
<!-- Open Graph: cómo se ve al compartir en redes -->
<meta property="og:title" content="Curso de HTML desde cero">
<meta property="og:description" content="Ejercicios guiados y examen final.">
<meta property="og:image" content="https://tecnicoweb.es/img/portada.png">
<meta property="og:type" content="website">
<meta name="twitter:card" content="summary_large_image">
Tres piezas clave del ejemplo: canonical declara la URL oficial cuando hay
duplicados (con/sin barra, con parámetros…); Open Graph controla título,
descripción e imagen cuando alguien comparte tu enlace en redes; y
twitter:card adapta esa vista a X/Twitter.
2.2 · Datos estructurados (JSON-LD)
Además de leer, Google quiere clasificar: «esto es un curso», «esta es su
valoración», «este es el precio». Eso se declara con vocabulario
schema.org en formato
JSON-LD (el que recomienda Google), dentro de una etiqueta
<script type="application/ld+json">:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Course",
"name": "Curso de HTML desde cero",
"description": "De principiante a nivel profesional en 4 bloques.",
"provider": {
"@type": "Organization",
"name": "Técnico Web",
"url": "https://tecnicoweb.es"
}
}
</script>
Y recuerda: el mejor SEO sigue siendo contenido único y de calidad con
URLs limpias (/curso-html/bloque-3 gana a /p?id=88&ref=x),
imágenes con alt descriptivo y una página rápida.
HTML5 avanzado: componentes que parecen mágicos
3.1 · <datalist>: autocompletado nativo
Un input de texto con sugerencias, sin una línea de JavaScript. El input
referencia la lista mediante list, y cada <option> es una
sugerencia (el usuario puede escribir algo distinto):
<label for="ciudad">Ciudad:</label>
<input type="text" id="ciudad" list="ciudades-es">
<datalist id="ciudades-es">
<option value="Madrid">
<option value="Barcelona">
<option value="Sevilla">
<option value="Bilbao">
</datalist>
3.2 · <details> + <summary>: acordeones nativos
Contenido desplegable sin JavaScript y accesible de serie. El atributo
booleano open lo deja expandido por defecto. De hecho, los temarios y las
soluciones de esta web funcionan exactamente así.
<details>
<summary>¿El curso es realmente gratuito?</summary>
<p>Sí: todos los bloques, ejercicios y exámenes son gratis.</p>
</details>
<details open>
<summary>¿Necesito saber programar?</summary>
<p>No. Empezamos desde cero absoluto.</p>
</details>
3.3 · SVG inline: gráficos que escalan solos
Un SVG escrito directamente en el HTML (inline) se pinta como parte de la
página: escala sin pixelarse, pesa poco y puedes colorearlo con CSS usando
currentColor. Para que sea accesible: role="img" +
<title>.
<svg viewBox="0 0 24 24" width="48" height="48"
role="img" aria-labelledby="titLogo">
<title id="titLogo">Logo de la tienda</title>
<path fill="currentColor"
d="M12 2 2 7v10l10 5 10-5V7L12 2z"></path>
</svg>
3.4 · <canvas>: lienzo dibujado con JavaScript
<canvas> crea un rectángulo en blanco que se dibuja mediante
JavaScript (juegos, gráficas, edición de imagen). Lo importante en HTML: su
contenido interno actúa como texto alternativo para navegadores antiguos
y lectores de pantalla.
<canvas id="grafica" width="400" height="200">
Tu navegador no soporta canvas: aquí va la tabla de datos.
</canvas>
<script>
// Esto es JavaScript (llegará en su propio curso 😉)
const lienzo = document.getElementById('grafica').getContext('2d');
lienzo.fillStyle = '#7c3aed';
lienzo.fillRect(20, 40, 80, 120); // barra morada
lienzo.fillStyle = '#e6c229';
lienzo.beginPath();
lienzo.arc(200, 100, 45, 0, Math.PI * 2); // círculo dorado
lienzo.fill();
</script>
| Elemento | Para qué sirve | Detalle clave |
|---|---|---|
<progress> | Barra de progreso de una tarea | value="70" max="100" |
<meter> | Medición dentro de un rango conocido | Disco usado, batería, nota |
<output> | Resultado de un cálculo | Compañero natural de formularios |
<picture> | Imágenes responsive por tipo/tamaño | Varios <source> + un <img> |
<dialog> | Ventana modal nativa | Se abre con showModal() vía JS |
Calidad medible: validación W3C y auditoría Lighthouse
Los profesionales no dicen «creo que mi página está bien»: lo demuestran con herramientas. Las dos imprescindibles son gratuitas y oficiales.
4.1 · El validador W3C
El validador Nu del W3C comprueba tu HTML contra el estándar. Flujo de trabajo:
- Enviar: pega la URL, sube el archivo o copia el código en validator.w3.org/nu.
- Leer errores primero: un solo error inicial (una etiqueta sin cerrar) suele arrastrar diez avisos posteriores.
- Corregir arriba abajo: arregla el primer error y revalida; verás cómo desaparecen en cascada.
- Objetivo: «Document checking completed. No errors or warnings to show.» 🟢
<ul> o <table> (solo admiten sus hijos válidos).<div> dentro de <p>, o un <h1> después de cerrar el contenedor de su sección.id repetidos) o valores sin comillas.<details> de estas páginas pasan la validación: cópialos como plantilla.4.2 · Lighthouse: tu inspector de calidad
Lighthouse viene integrado en Chrome (F12 → pestaña Lighthouse) y audita tu página devolviendo notas de 0 a 100 en cuatro categorías:
| Categoría | Qué analiza | Meta profesional |
|---|---|---|
| Performance | Peso, imágenes modernas, carga | ≥ 90 |
| Accessibility | Contraste, alts, labels, ARIA, idioma | ≥ 95 |
| Best practices | HTTPS, consola limpia, errores JS | ≥ 95 |
| SEO | Title, meta description, links rastreables | 100 |
Con esto cierras el ciclo profesional: construyes accesible → optimizas para buscadores → usas HTML5 avanzado → y demuestras la calidad con evidencias. Es exactamente lo que auditará el examen de este bloque.
✍️ Los 10 ejercicios del bloque
Del 1 al 9 practicas las lecciones; el 10 es el reto del bloque: una auditoría real de principio a fin.
1 Rescatar el botón hamburguesa
Este menú móvil funciona con ratón pero es invisible para teclado y lectores de pantalla. Reconviértelo en algo accesible conservando el mismo aspecto previsto (la clase se queda igual):
<div class="burger" onclick="toggleNav()">☰</div>
Ver solución
<button class="burger"
type="button"
aria-label="Abrir menú de navegación"
aria-expanded="false"
aria-controls="navPrincipal">
☰
</button>
<!-- Al abrir/cerrar desde JS se alternaría también: -->
<!-- aria-expanded="true|false" y el aria-label Abrir/Cerrar -->
2 Galería con alts que enseñan
Escribe una galería <figure> de tres fotos de un viaje: dos informativas
(describen la foto concreta) y una puramente decorativa (línea divisoria gráfica).
Usa <figcaption> en la primera.
Ver solución
<figure>
<img src="fotos/playa.jpg"
alt="Atardecer en la playa de Bolonia con el dunar al fondo">
<figcaption>Bolonia, agosto de 2026</figcaption>
</figure>
<figure>
<img src="fotos/mercado.jpg"
alt="Puesto de frutas del mercado central con mangos y papayas">
</figure>
<!-- Decorativa: el lector de pantalla debe ignorarla -->
<img src="fotos/separador-olas.svg" alt="">
3 Plantilla con skip link
Partiendo de una página con header + main + footer, añade el enlace «Saltar al contenido» con su CSS mínimo y haz que el destino principal sea enfocable.
Ver solución
<body>
<a class="skip-link" href="#principal">Saltar al contenido</a>
<header>…</header>
<main id="principal" tabindex="-1">
<h1>Bienvenida a mi portfolio</h1>
</main>
<footer>…</footer>
</body>
4 Formulario que habla claro
Campo de «Contraseña» con pista de ayuda («Mínimo 8 caracteres») vinculada mediante
aria-describedby, agrupado en un <fieldset> con
<legend> junto a un campo de email con su label.
Ver solución
<fieldset>
<legend>Credenciales de acceso</legend>
<label for="email">Email</label>
<input type="email" id="email" required>
<label for="pass">Contraseña</label>
<input type="password" id="pass"
aria-describedby="ayudaPass" required>
<small id="ayudaPass">Mínimo 8 caracteres, con un número.</small>
</fieldset>
5 Head completo: Clínica Dental Sonrisa
Escribe el <head> completo (SEO + Open Graph) para la home de
«Clínica Dental Sonrisa» en Valencia: título con localismo, description comercial,
canonical, imagen de portada sonrisa-portada.png y tarjeta Twitter grande.
Ver solución
<title>Clínica Dental Sonrisa en Valencia | Dentistas de confianza</title>
<meta name="description"
content="Ortodoncia, implantes y limpiezas en Valencia. Primera consulta
gratis con los dentistas de Clínica Dental Sonrisa.">
<link rel="canonical" href="https://dentalsorrisa.es/">
<meta property="og:title" content="Clínica Dental Sonrisa · Valencia">
<meta property="og:description" content="Primera consulta gratis.">
<meta property="og:image" content="https://dentalsorrisa.es/sonrisa-portada.png">
<meta property="og:type" content="website">
<meta name="twitter:card" content="summary_large_image">
6 Datos estructurados de receta
Crea el JSON-LD @type: «Recipe» para la «Tortilla de patatas de la abuela»,
con autor, 35 minutos de preparación y rendición para 4 personas.
Ver solución
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Recipe",
"name": "Tortilla de patatas de la abuela",
"author": { "@type": "Person", "name": "Rosa García" },
"totalTime": "PT35M",
"recipeYield": "4 raciones"
}
</script>
<!-- PT35M = 35 minutos en formato ISO 8601 -->
7 Encuesta con datalist, meter y progress
Página de encuesta tecnológica: ciudad con <datalist>, satisfacción
mostrada con <meter> (7 de 10) y progreso de participación con
<progress> (820 de 1000 respuestas).
Ver solución
<label for="ciudadEncuesta">Tu ciudad</label>
<input type="text" id="ciudadEncuesta" list="listaCiudades">
<datalist id="listaCiudades">
<option value="Madrid">
<option value="Barcelona">
<option value="Sevilla">
</datalist>
<p><label>Satisfacción media:
<meter value="7" min="0" max="10">7 de 10</meter>
</label></p>
<p><label>Participación:
<progress value="820" max="1000">82 %</progress>
</label></p>
8 Icono SVG accesible y temable
Icono de estrella para un sistema de valoraciones: SVG inline con currentColor,
role="img" y <title>, de forma que herede el color del texto
donde se inserte.
Ver solución
<svg viewBox="0 0 24 24" width="24" height="24"
role="img" aria-labelledby="titEstrella">
<title id="titEstrella">Valoración: 4 de 5 estrellas</title>
<path fill="currentColor"
d="M12 2l2.9 6.3 6.9.8-5.1 4.7 1.4 6.8L12 17.2 5.9 20.6l1.4-6.8L2.2 9.1l6.9-.8z"></path>
</svg>
<!-- En CSS bastará: .valoracion { color: gold } -->
9 Depurando al estándar
Este fragmento da errores en el W3C. Localízalos todos y reescribe la versión válida:
<ul>
Opciones disponibles:
<li><a href="#">Descargas</li></a>
<p><li><a href="#">Soporte</a></li></p>
</ul>
Ver solución
Errores: texto suelto dentro de <ul>; etiquetas cruzadas
(</li></a> en orden inverso); y un <p> envolviendo
elementos de lista, que no es hijo válido de <ul>.
<p>Opciones disponibles:</p>
<ul>
<li><a href="#">Descargas</a></li>
<li><a href="#">Soporte</a></li>
</ul>
10 🔥 Reto: auditoría real de principio a fin
Elige una página pública tuya (o genera una con errores a propósito). Ejecuta el validador W3C y una auditoría Lighthouse mobile, anota los fallos principales, corrígelos y documenta el antes/después con capturas de las notas. Este ejercicio alimenta directamente la Parte B del examen.
Ver proceso modelo
- Línea base: captura las notas iniciales de Lighthouse (Performance, Accessibility, Best Practices, SEO) y guarda el informe.
- W3C primero: corrige errores de estructura; muchos avisos de accesibilidad desaparecen solos después.
- Accesibilidad: alts, labels, contraste y skip link según la Lección 1.
- SEO: title, description, canonical y Open Graph según la Lección 2.
- Re-auditoría: repite Lighthouse y compara. Documenta cada cambio y su impacto.
📝 Examen del Bloque 3
Test teórico autoevaluable (mínimo 70 %) + reto práctico de auditoría con checklist de autocorrección.
Parte B · Reto práctico: tu primera auditoría
Completa el ejercicio 10 sobre tu propia página sin atajos. Mínimo para aprobar: 7 de 8 casillas. El laboratorio comprueba automáticamente los criterios estructurales; las capturas de Lighthouse (casillas 2 y 8) las marcas tú.
Cumplidos: 0 / 8