Módulo 06: HTML5 semántico
Objetivos del módulo
- Entender qué es la semántica en HTML y por qué importa tanto
- Usar correctamente
header,nav,main,article,section,asideyfooter - Saber elegir entre
article,sectionydiv - Conocer otros elementos semánticos:
address,details/summary,dialog,figure,time,markysearch - Diseñar el esquema típico de una página web
- Comprender los roles implícitos y las regiones (landmarks)
- Aplicar las bases de la accesibilidad: textos alternativos, orden de encabezados, contraste, teclado y lectores de pantalla
- Usar ARIA de forma responsable, empezando por su primera regla
- Optimizar el SEO on-page:
title, meta descripción, Open Graph, favicon y datos estructurados JSON-LD - Maquetar un blog completo con HTML semántico
1. ¿Por qué la semántica?
📘 Concepto: Semántica significa “relativo al significado”. Un HTML semántico usa cada etiqueta según lo que significa el contenido, no según cómo se ve. Un
<nav>dice “esto es navegación”, un<article>dice “esto es un contenido independiente”, un<button>dice “esto es un botón”. Un<div>no dice nada.
Imagina dos supermercados. En el primero, todos los productos están en cajas marrones idénticas sin etiquetar: para encontrar el arroz hay que abrirlas una a una. En el segundo, cada pasillo tiene un cartel (“Lácteos”, “Conservas”, “Limpieza”) y cada producto su etiqueta. Ambos tienen los mismos productos, pero solo en el segundo se encuentra algo rápidamente. Un HTML hecho solo con div es el primer supermercado.
Lo mismo, con y sin semántica
<!-- ❌ "Sopa de divs": funciona visualmente, pero no significa nada -->
<div class="cabecera">
<div class="logo">Mi Blog</div>
<div class="menu">
<div><a href="/">Inicio</a></div>
<div><a href="/blog">Blog</a></div>
</div>
</div>
<div class="contenido">
<div class="post">
<div class="titulo">Mi primer artículo</div>
<div class="texto">Lorem ipsum...</div>
</div>
</div>
<div class="pie">© 2026</div>
<!-- ✅ Semántico: cada parte dice lo que es -->
<header>
<p class="logo">Mi Blog</p>
<nav>
<ul>
<li><a href="/">Inicio</a></li>
<li><a href="/blog">Blog</a></li>
</ul>
</nav>
</header>
<main>
<article>
<h1>Mi primer artículo</h1>
<p>Lorem ipsum...</p>
</article>
</main>
<footer>
<p><small>© 2026</small></p>
</footer>
Visualmente pueden ser idénticos (con CSS). Pero para todo lo demás, son mundos distintos.
Quién se beneficia
| Beneficiario | Cómo aprovecha la semántica |
|---|---|
| Usuarios de lectores de pantalla | Saltan directamente al menú, al contenido principal o al siguiente artículo; oyen “navegación”, “artículo”, “botón” |
| Usuarios de teclado | Los elementos nativos (button, a, input) son enfocables y funcionan con Intro y Espacio sin programar nada |
| Buscadores | Entienden qué es el contenido principal, qué es un artículo, qué es navegación repetida |
| Desarrolladores | El código se lee y se mantiene mucho mejor |
💡 Consejo: Antes de escribir un
div, pregúntate: “¿hay alguna etiqueta que diga exactamente qué es esto?”. Si la hay, úsala. Eldives el último recurso, no el primero.
2. Elementos estructurales de HTML5
header: cabecera
<header> representa el contenido introductorio de la página o de una sección: logotipo, título, buscador, navegación principal…
<!-- Cabecera de la página -->
<header>
<a href="/" class="logo">
<img src="img/logo.svg" alt="Librería Papel y Tinta: inicio" width="160" height="40">
</a>
<nav aria-label="Principal">...</nav>
</header>
- Puede haber varios
headeren una página (uno general y uno por artículo). - No puede ir dentro de otro
header, de unfooterni de unaddress. headerno es lo mismo quehead:headcontiene metadatos invisibles;headeres contenido visible delbody.
nav: navegación
<nav> agrupa los bloques principales de enlaces de navegación: menú principal, migas de pan, índice de contenidos, paginación.
<nav aria-label="Principal">
<ul>
<li><a href="/" aria-current="page">Inicio</a></li>
<li><a href="/catalogo">Catálogo</a></li>
<li><a href="/eventos">Eventos</a></li>
<li><a href="/contacto">Contacto</a></li>
</ul>
</nav>
⚠️ Importante: No todos los grupos de enlaces son un
nav. Los enlaces legales del pie o los iconos de redes sociales normalmente no lo necesitan. Reservanavpara la navegación importante. Y si hay variosnaven la página, distínguelos conaria-label(“Principal”, “Migas de pan”, “Pie”): el lector de pantalla los anunciará como “navegación Principal”, “navegación Migas de pan”…
💡 Consejo:
aria-current="page"indica cuál es la página actual dentro del menú. Los lectores de pantalla lo anuncian (“página actual”) y puedes usarlo en CSS para resaltarla:[aria-current="page"] { font-weight: bold; }.
main: contenido principal
<main> contiene el contenido principal y único de la página: lo que la hace diferente de las demás páginas del sitio. Quedan fuera la cabecera, el menú, la barra lateral y el pie, que se repiten en todo el sitio.
<body>
<header>...</header>
<main id="contenido">
<h1>Catálogo de novela</h1>
...
</main>
<footer>...</footer>
</body>
Regla de main | |
|---|---|
Solo un main visible por página | Si hay más, los demás deben llevar hidden |
No puede estar dentro de article, aside, header, footer ni nav | Es contenido de primer nivel |
| Debe ser el destino del enlace “Saltar al contenido” | Ver sección 6 |
article: contenido independiente
📘 Concepto:
<article>es un contenido autónomo e independiente que tendría sentido por sí solo, fuera de la página: podría publicarse en otro sitio, enviarse por correo o distribuirse por RSS y se seguiría entendiendo.
Ejemplos: una entrada de blog, una noticia, un comentario de un usuario, una ficha de producto, una tarjeta de una oferta de empleo, una publicación en un foro.
<article>
<header>
<h2>Cómo cuidar tus libros antiguos</h2>
<p><time datetime="2026-09-15">15 de septiembre de 2026</time></p>
</header>
<p>La humedad es el peor enemigo del papel...</p>
<footer>
<p>Etiquetas: <a href="/etiquetas/conservacion">conservación</a></p>
</footer>
</article>
section: sección temática
<section> agrupa contenido temáticamente relacionado, normalmente con su propio encabezado. Es como un capítulo de un libro.
<main>
<h1>Club de lectura</h1>
<section aria-labelledby="titulo-como">
<h2 id="titulo-como">Cómo funciona</h2>
<p>Cada mes elegimos un libro...</p>
</section>
<section aria-labelledby="titulo-proximas">
<h2 id="titulo-proximas">Próximas reuniones</h2>
<p>...</p>
</section>
</main>
⚠️ Importante:
sectionno es undivcon otro nombre. Si solo necesitas un contenedor para aplicar estilos (por ejemplo, para centrar o crear una columna), usadiv. La regla práctica: si no puedes ponerle un encabezado con sentido, probablemente no es unasection.
article, section o div
flowchart TD
A{¿El contenido tiene sentido<br/>por sí solo, fuera de la página?} -->|Sí| B[article]
A -->|No| C{¿Es un bloque temático<br/>con su propio encabezado?}
C -->|Sí| D[section]
C -->|No| E{¿Lo necesitas solo para<br/>estilos o JavaScript?}
E -->|Sí| F[div]
E -->|No| G[Quizá no necesitas<br/>ningún contenedor]
| Elemento | Pregunta clave | Ejemplo |
|---|---|---|
article | ¿Lo podría publicar otra web tal cual? | Una noticia, un comentario, un producto |
section | ¿Es un capítulo con título dentro de algo mayor? | “Características”, “Opiniones”, “Preguntas frecuentes” |
div | ¿Lo necesito solo para maquetar? | Un contenedor para centrar o una fila de una rejilla |
💡 Consejo: Un
articlepuede contenersection(los apartados de una entrada larga) y unasectionpuede contenerarticle(una sección “Últimas noticias” con varias noticias). Ambas combinaciones son correctas.
aside: contenido complementario
<aside> contiene información relacionada indirectamente con el contenido que la rodea: si la eliminas, el contenido principal se sigue entendiendo. Ejemplos: barra lateral, artículos relacionados, glosario, biografía del autor, publicidad, una nota al margen.
<main>
<article>
<h1>La historia de la imprenta</h1>
<p>Johannes Gutenberg desarrolló la imprenta de tipos móviles hacia 1450...</p>
</article>
<!-- Barra lateral de la página -->
<aside aria-label="Artículos relacionados">
<h2>También te puede interesar</h2>
<ul>
<li><a href="/blog/papiro">Del papiro al pergamino</a></li>
<li><a href="/blog/libro-electronico">El libro electrónico</a></li>
</ul>
</aside>
</main>
footer: pie
<footer> contiene información sobre su sección o la página: autoría, copyright, enlaces legales, datos de contacto, enlaces relacionados.
<footer>
<nav aria-label="Pie">
<ul>
<li><a href="/aviso-legal">Aviso legal</a></li>
<li><a href="/privacidad">Privacidad</a></li>
<li><a href="/cookies">Cookies</a></li>
</ul>
</nav>
<address>
Librería Papel y Tinta · Calle Mayor, 12 · 50001 Zaragoza ·
<a href="tel:+34976000000">976 00 00 00</a> ·
<a href="mailto:hola@papelytinta.es">hola@papelytinta.es</a>
</address>
<p><small>© 2026 Librería Papel y Tinta</small></p>
</footer>
search: zona de búsqueda
<search> es un elemento reciente que agrupa los controles de búsqueda o filtrado. Funciona ya en todos los navegadores principales.
<search>
<form action="/buscar" method="get">
<label for="q">Buscar libros</label>
<input type="search" id="q" name="q">
<button type="submit">Buscar</button>
</form>
</search>
hgroup: encabezado con subtítulo
<hgroup> agrupa un encabezado con uno o varios párrafos de subtítulo (<hgroup><h1>Papel y Tinta</h1><p>Librería independiente desde 1987</p></hgroup>), sin que el subtítulo cuente como un encabezado más. No uses un h2 como subtítulo de un h1 solo porque “queda bien”: crearías una sección falsa en el esquema del documento.
3. El esquema típico de una página
La mayoría de las webs siguen una estructura muy parecida:
flowchart TB
subgraph BODY["body"]
direction TB
subgraph HEADER["header"]
LOGO["logo"]
NAV["nav"]
end
subgraph MAIN["main"]
direction LR
subgraph ART["article"]
AH["header: h1, autor, fecha"]
S1["section: h2 + contenido"]
S2["section: h2 + contenido"]
AF["footer: etiquetas, compartir"]
end
ASIDE["aside: relacionados"]
end
FOOTER["footer: legal, contacto, address"]
end
📘 Concepto: La colocación visual (menú a la izquierda, barra lateral a la derecha, columnas) no depende del HTML, sino del CSS (Flexbox y Grid, módulos 10 y 11). El HTML solo define qué es cada parte y en qué orden lógico se lee. Ese orden debe tener sentido leído de arriba abajo, porque es el que siguen los lectores de pantalla y el teclado.
4. Otros elementos semánticos
address: información de contacto
<address> contiene la información de contacto del autor o propietario del article o de la página más cercanos: nombre, email, teléfono, dirección, redes…
<article>
<h2>Entrevista a una encuadernadora</h2>
<p>...</p>
<footer>
<address>
Escrito por <a href="mailto:marta@papelytinta.es">Marta Sanz</a>.
Síguela en <a href="https://mastodon.social/@martasanz">Mastodon</a>.
</address>
</footer>
</article>
⚠️ Importante:
addressno es para cualquier dirección postal. Si en un texto mencionas “la reunión será en la calle Alcalá, 45”, eso es un párrafo normal.addresses solo para los datos de contacto de quien es responsable del contenido.
details y summary: contenido desplegable
Un acordeón nativo, sin JavaScript: summary es el título visible y, al hacer clic, se muestra el resto del contenido.
<h2>Preguntas frecuentes</h2>
<details>
<summary>¿Hacéis envíos a Canarias?</summary>
<p>Sí, con un recargo de 6 € y un plazo de 5 a 7 días laborables.</p>
</details>
<details open>
<summary>¿Puedo devolver un libro?</summary>
<p>Tienes 30 días desde la recepción. El libro debe estar en perfecto estado.</p>
</details>
| Atributo | Efecto |
|---|---|
open | El contenido aparece desplegado por defecto |
name | Los details con el mismo name forman un grupo exclusivo: al abrir uno se cierran los demás |
<!-- Acordeón exclusivo: solo uno abierto a la vez -->
<details name="faq-pagos">
<summary>¿Qué formas de pago aceptáis?</summary>
<p>Tarjeta, Bizum y transferencia.</p>
</details>
<details name="faq-pagos">
<summary>¿Es seguro pagar con tarjeta?</summary>
<p>Sí, el pago se realiza en la pasarela segura del banco.</p>
</details>
💡 Consejo:
detailses accesible por defecto: se abre con el teclado (IntrooEspaciosobre elsummary) y los lectores de pantalla anuncian si está “expandido” o “contraído”. Además, el texto oculto se encuentra con la búsqueda del navegador (Ctrl + F), que lo despliega automáticamente.
dialog: ventanas de diálogo
<dialog> representa una ventana de diálogo o modal (como una ventana de confirmación). Para abrirla como modal se necesita una línea de JavaScript:
<button type="button" id="abrir-dialogo">Suscribirme al boletín</button>
<dialog id="dialogo-boletin" aria-labelledby="titulo-dialogo">
<h2 id="titulo-dialogo">Suscríbete al boletín</h2>
<form method="dialog">
<label for="email-boletin">Email</label>
<input type="email" id="email-boletin" name="email">
<!-- Un formulario con method="dialog" cierra el diálogo al enviarse -->
<button value="cancelar">Cancelar</button>
<button value="ok">Suscribirme</button>
</form>
</dialog>
<script>
const dialogo = document.querySelector("#dialogo-boletin");
document.querySelector("#abrir-dialogo").addEventListener("click", () => {
dialogo.showModal(); // Abre como modal: bloquea el resto de la página
});
</script>
Con el atributo open el diálogo se muestra no modal (el resto de la página sigue siendo usable). Con showModal() es modal: oscurece el fondo (pseudoelemento ::backdrop), atrapa el foco dentro y se cierra con Esc. Se cierra con close() o enviando un form method="dialog".
figure, time y mark (repaso semántico)
Ya los conoces de módulos anteriores. Recuerda su significado:
| Elemento | Significado | Ejemplo |
|---|---|---|
figure + figcaption | Contenido autónomo referenciado (imagen, gráfico, código, cita) con su leyenda | Un gráfico de ventas con su pie |
time | Fecha u hora con valor legible por máquinas en datetime | <time datetime="2026-12-24">Nochebuena</time> |
mark | Texto resaltado por su relevancia en el contexto actual | Los términos buscados en una página de resultados |
⚠️ Importante:
markno es para “destacar porque es importante” (eso esstrong), sino para resaltar algo relevante para lo que el usuario está haciendo ahora, como un subrayador fosforito.
5. Roles implícitos y regiones (landmarks)
📘 Concepto: Cada elemento HTML tiene un rol implícito que el navegador comunica a las tecnologías de asistencia a través del árbol de accesibilidad (una versión del DOM pensada para lectores de pantalla). Algunos roles son regiones (landmarks): zonas grandes de la página a las que el usuario puede saltar directamente, como los capítulos de un audiolibro.
| Elemento | Rol implícito | ¿Es región? | Notas |
|---|---|---|---|
header (hijo de body) | banner | Sí | Dentro de article, section, aside, nav o main no es región |
nav | navigation | Sí | |
main | main | Sí | |
aside | complementary | Sí | |
footer (hijo de body) | contentinfo | Sí | Mismas condiciones que header |
section | region | Solo si tiene nombre accesible (aria-label o aria-labelledby) | |
form | form | Solo si tiene nombre accesible | |
search | search | Sí | |
article | article | No | |
button | button | No | Enfocable y activable con teclado |
a con href | link | No | Sin href, no es un enlace |
ul, ol | list | No | |
h1-h6 | heading (con nivel) | No | Navegación por encabezados |
img con alt | img | No | Con alt="", se ignora |
💡 Consejo: Puedes ver el árbol de accesibilidad en Chrome: DevTools → Elements → pestaña Accessibility (en el panel lateral). Selecciona un elemento y verás su rol, su nombre accesible y sus propiedades.
Cómo navega un usuario de lector de pantalla
Por ejemplo, en NVDA la tecla H salta al siguiente encabezado, D a la siguiente región, F al siguiente campo de formulario e Insert + F7 muestra la lista de enlaces; en VoiceOver se usa el rotor (Control + Option + U).
📘 Concepto: Los usuarios de lectores de pantalla casi nunca leen la página de principio a fin. Igual que tú escaneas visualmente los titulares, ellos saltan de encabezado en encabezado o de región en región. Si tu página no tiene encabezados ni regiones, es como un libro sin índice ni capítulos.
6. Accesibilidad: lo esencial
📘 Concepto: La accesibilidad web (a11y, abreviatura de las 11 letras entre la “a” y la “y” de accessibility) consiste en que todas las personas puedan percibir, entender, navegar e interactuar con la web, incluidas las personas con discapacidad visual, auditiva, motora o cognitiva, y también las personas mayores, quienes usan el móvil a pleno sol o quien tiene un brazo escayolado.
Las WCAG (Web Content Accessibility Guidelines) del W3C son la referencia internacional. La versión actual es WCAG 2.2 (2023), con tres niveles de conformidad: A, AA (el exigido normalmente por la ley) y AAA.
⚠️ Importante: La accesibilidad no es opcional. En la Unión Europea, la Ley Europea de Accesibilidad (European Accessibility Act) obliga desde el 28 de junio de 2025 a que muchos productos y servicios digitales (comercio electrónico, banca, transporte, libros electrónicos…) sean accesibles. En España se traspuso mediante la Ley 11/2023, y el sector público ya estaba obligado por el Real Decreto 1112/2018.
Recuerda del módulo 03 que toda imagen lleva alt: descriptivo si informa, la acción si es funcional y vacío si es decorativa. A eso se suman estas bases:
6.1. Orden de encabezados
<!-- ✅ Jerarquía correcta -->
<h1>Recetas de otoño</h1>
<h2>Sopas</h2>
<h3>Crema de calabaza</h3>
<h3>Sopa de castañas</h3>
<h2>Postres</h2>
<h3>Buñuelos</h3>
<!-- ❌ Saltos y encabezados usados por su tamaño -->
<h1>Recetas de otoño</h1>
<h4>Sopas</h4>
<h2>Crema de calabaza</h2>
6.2. Contraste de color
El texto debe tener suficiente contraste con su fondo para que lo puedan leer personas con baja visión o en condiciones de luz difíciles.
| Elemento | Nivel AA | Nivel AAA |
|---|---|---|
| Texto normal | 4,5 : 1 | 7 : 1 |
| Texto grande (24 px o más, o 18,66 px o más en negrita) | 3 : 1 | 4,5 : 1 |
| Componentes de interfaz y gráficos (bordes de campos, iconos) | 3 : 1 | — |
| Combinación | Contraste aproximado | ¿Cumple AA para texto normal? |
|---|---|---|
Negro #000000 sobre blanco | 21 : 1 | Sí |
Gris #767676 sobre blanco | 4,5 : 1 | Sí (justo) |
Gris claro #999999 sobre blanco | 2,8 : 1 | No |
💡 Consejo: Comprueba el contraste en DevTools: en el panel Styles, haz clic en el cuadradito de color de una propiedad
colory verás el ratio de contraste con marcas de AA y AAA. También puedes usar el WebAIM Contrast Checker (webaim.org/resources/contrastchecker).
⚠️ Importante: Nunca transmitas información solo con el color (“los campos en rojo son obligatorios”). Acompáñalo siempre de texto, iconos o patrones.
6.3. Navegación por teclado
Muchas personas no usan ratón: personas ciegas, personas con movilidad reducida, usuarios avanzados… Todo lo que se pueda hacer con el ratón debe poder hacerse con el teclado.
| Tecla | Acción |
|---|---|
Tab / Shift + Tab | Siguiente / anterior elemento enfocable |
Intro | Activar enlaces y botones |
Espacio | Activar botones, marcar casillas, desplazar la página |
| Flechas | Moverse dentro de grupos de radio, select, menús |
Esc | Cerrar diálogos y menús |
Reglas fundamentales:
- Usa elementos nativos (
a,button,input,select,details): ya son enfocables y funcionan con el teclado. - El orden del foco sigue el orden del HTML: escribe el HTML en un orden lógico.
- El foco debe ser siempre visible: no elimines el
outlinesin sustituirlo. - Nunca uses
tabindexcon valores positivos (tabindex="5"): rompe el orden natural.
Valor de tabindex | Efecto |
|---|---|
0 | Hace enfocable un elemento que no lo es, en su orden natural |
-1 | Enfocable solo mediante JavaScript, no con Tab (útil para destinos de enlaces internos) |
Positivo (1, 2…) | No lo uses: altera el orden de toda la página |
6.4. Enlace “Saltar al contenido”
Un usuario de teclado tendría que pulsar Tab decenas de veces para atravesar el menú en cada página. La solución es un enlace al principio del body que salte directamente al main:
<body>
<a href="#contenido" class="saltar">Saltar al contenido principal</a>
<header>...menú con 20 enlaces...</header>
<main id="contenido" tabindex="-1">
...
</main>
</body>
/* El enlace está oculto hasta que recibe el foco con el teclado */
.saltar {
position: absolute;
left: -9999px;
}
.saltar:focus {
left: 16px;
top: 16px;
background: #1e3a8a;
color: white;
padding: 8px 16px;
z-index: 1000;
}
✏️ Ejercicio: Entra en la web de un organismo público (por ejemplo, la de un ministerio o la de tu ayuntamiento) y pulsa
Tabnada más cargar. ¿Aparece un enlace “Saltar al contenido” o similar? Sigue pulsandoTaby comprueba si siempre ves dónde está el foco.
6.5. Lectores de pantalla
| Lector | Sistema | Precio | Cómo activarlo |
|---|---|---|---|
| NVDA | Windows | Gratuito | Descarga en nvaccess.org |
| JAWS | Windows | De pago | — |
| Narrador | Windows | Incluido | Ctrl + Win + Intro |
| VoiceOver | macOS, iOS | Incluido | Cmd + F5 (Mac); Ajustes → Accesibilidad (iPhone) |
| TalkBack | Android | Incluido | Ajustes → Accesibilidad |
💡 Consejo: Dedica 15 minutos a navegar tu propia página con NVDA o VoiceOver con los ojos cerrados. Es la prueba de accesibilidad más reveladora que existe y te hará escribir mejor HTML para siempre.
7. ARIA: lo básico
📘 Concepto: WAI-ARIA (Accessible Rich Internet Applications) es una especificación del W3C que añade a HTML atributos para describir roles, estados y propiedades a las tecnologías de asistencia. Sirve para hacer accesibles componentes que HTML no tiene de forma nativa (pestañas, menús desplegables complejos, carruseles…). ARIA solo cambia lo que se anuncia: no cambia el comportamiento ni el aspecto.
La primera regla de ARIA
⚠️ Importante: La primera regla de ARIA dice: “Si puedes usar un elemento o atributo HTML nativo con la semántica y el comportamiento que necesitas, úsalo en lugar de reutilizar otro elemento y añadirle un rol, estado o propiedad ARIA.” Dicho de otra forma: la mejor ARIA es la que no necesitas. Y un corolario muy citado: ninguna ARIA es mejor que una ARIA mal usada.
<!-- ❌ ARIA innecesaria y además incompleta: falta el teclado, el foco... -->
<div role="button" onclick="enviar()">Enviar</div>
<!-- ✅ HTML nativo -->
<button type="button" onclick="enviar()">Enviar</button>
Los atributos ARIA más útiles
| Atributo | Para qué | Ejemplo |
|---|---|---|
aria-label | Da un nombre accesible cuando no hay texto visible | <button aria-label="Cerrar">×</button> |
aria-labelledby | Toma el nombre de otro elemento (por su id) | <section aria-labelledby="titulo-ofertas"> |
aria-describedby | Añade una descripción adicional | Textos de ayuda de formularios |
aria-hidden="true" | Oculta el elemento a las tecnologías de asistencia | Iconos decorativos |
aria-current | Elemento actual en un conjunto | aria-current="page" en el menú |
aria-expanded | Indica si un elemento controlado está desplegado | Botón de menú hamburguesa |
aria-controls | Indica qué elemento controla | aria-controls="menu-principal" |
aria-live | Anuncia cambios dinámicos en una zona | aria-live="polite" en mensajes de estado |
aria-invalid | Campo con error | Formularios |
<!-- Botón de icono: sin texto visible, necesita nombre accesible -->
<button type="button" aria-label="Abrir menú" aria-expanded="false" aria-controls="menu-movil">
<svg width="24" height="24" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path fill="currentColor" d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/>
</svg>
</button>
<!-- Zona que anuncia los mensajes sin mover el foco -->
<p role="status" aria-live="polite">Producto añadido al carrito.</p>
⚠️ Importante:
aria-hidden="true"nunca debe ponerse en un elemento enfocable (un enlace o un botón), ni en un contenedor que los incluya: el usuario de teclado llegaría a un elemento que el lector de pantalla no anuncia, como un fantasma.
8. SEO on-page
📘 Concepto: El SEO (Search Engine Optimization) es el conjunto de prácticas para que una página aparezca bien posicionada en los buscadores. El SEO on-page es la parte que depende del propio HTML de la página: título, descripción, encabezados, semántica, rendimiento, datos estructurados…
title y meta descripción
<head>
<title>Crema de calabaza asada: receta fácil en 30 minutos | Cocina de Otoño</title>
<meta name="description" content="Aprende a hacer una crema de calabaza asada suave y cremosa con solo 6 ingredientes. Receta paso a paso, apta para veganos.">
</head>
| Elemento | Recomendación |
|---|---|
title | Único por página, descriptivo, unos 50-60 caracteres, lo más importante al principio |
meta description | Resumen atractivo de unos 150-160 caracteres; no influye directamente en la posición, pero sí en que el usuario haga clic |
meta keywords | No lo uses: Google lo ignora desde hace muchos años |
Otras etiquetas del head útiles para SEO
<!-- URL canónica: la versión "oficial" de la página si hay duplicados -->
<link rel="canonical" href="https://cocinadeotono.es/recetas/crema-calabaza">
<!-- Indicaciones para los robots de los buscadores -->
<meta name="robots" content="index, follow"> <!-- Por defecto: indexar y seguir enlaces -->
<meta name="robots" content="noindex"> <!-- No mostrar esta página en buscadores -->
Open Graph y tarjetas para redes sociales
Cuando alguien comparte tu página en WhatsApp, LinkedIn, Mastodon o Facebook, se genera una vista previa con título, descripción e imagen. Esa información sale de las etiquetas Open Graph:
<meta property="og:type" content="article">
<meta property="og:title" content="Crema de calabaza asada: receta fácil">
<meta property="og:description" content="Suave, cremosa y lista en 30 minutos.">
<meta property="og:image" content="https://cocinadeotono.es/img/og/crema-calabaza.jpg">
<meta property="og:image:alt" content="Cuenco de crema de calabaza con pipas tostadas">
<meta property="og:url" content="https://cocinadeotono.es/recetas/crema-calabaza">
<meta property="og:site_name" content="Cocina de Otoño">
<meta property="og:locale" content="es_ES">
<!-- Tarjeta de X (Twitter) con imagen grande -->
<meta name="twitter:card" content="summary_large_image">
| Propiedad | Notas |
|---|---|
og:type | website para la portada, article para artículos |
og:image | URL absoluta; tamaño recomendado 1200 × 630 px |
og:url | URL canónica de la página |
⚠️ Importante: Fíjate en que Open Graph usa el atributo
property, noname. Y las URLs (sobre todo la de la imagen) deben ser absolutas:https://..., nuncaimg/foto.jpg.
Favicon e iconos
<!-- Icono clásico (los navegadores también lo buscan en /favicon.ico) -->
<link rel="icon" href="/favicon.ico" sizes="32x32">
<!-- Icono vectorial: nítido a cualquier tamaño -->
<link rel="icon" href="/icon.svg" type="image/svg+xml">
<!-- Icono al añadir la web a la pantalla de inicio del iPhone (180 × 180 px) -->
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
💡 Consejo: Con un
favicon.ico, unicon.svgy unapple-touch-icon.pngcubres prácticamente todos los casos. No necesitas las decenas de tamaños que generaban las herramientas antiguas.
Datos estructurados con JSON-LD
📘 Concepto: Los datos estructurados describen el contenido de la página en un formato que las máquinas entienden sin ambigüedad, usando el vocabulario de Schema.org. Gracias a ellos, Google puede mostrar resultados enriquecidos: estrellas de valoración, tiempo de preparación de una receta, fechas de eventos, preguntas frecuentes… El formato recomendado es JSON-LD, un bloque
scripten elheado en elbody.
Así es un bloque JSON-LD mínimo para una entrada de blog (verás uno completo en el ejemplo de la sección 9):
{
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": "Crema de calabaza asada: receta fácil en 30 minutos",
"datePublished": "2026-10-03T09:00:00+02:00",
"author": { "@type": "Person", "name": "Marta Sanz" }
}
| Tipo de Schema.org | Uso |
|---|---|
Article, BlogPosting, NewsArticle | Artículos y entradas de blog |
Organization, LocalBusiness | Datos de una empresa o un negocio local (dirección, horario) |
Product + Offer | Productos con precio y disponibilidad |
Recipe | Recetas (ingredientes, tiempos, calorías) |
💡 Consejo: Comprueba tus datos estructurados con la Prueba de resultados enriquecidos de Google (
search.google.com/test/rich-results) y con el validador de Schema.org (validator.schema.org). Y recuerda: los datos estructurados deben coincidir con el contenido visible de la página; no inventes valoraciones ni datos que el usuario no pueda ver.
9. Ejemplo completo: un blog semántico
💻 Ejemplo:
blog/crema-calabaza.html, una entrada de blog con todo lo aprendido en el bloque de HTML:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Crema de calabaza asada: receta fácil en 30 minutos | Cocina de Otoño</title>
<meta name="description" content="Aprende a hacer una crema de calabaza asada suave y cremosa con solo 6 ingredientes. Receta paso a paso, apta para veganos.">
<link rel="canonical" href="https://cocinadeotono.es/blog/crema-calabaza">
<!-- Iconos -->
<link rel="icon" href="/favicon.ico" sizes="32x32">
<link rel="icon" href="/icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<!-- Open Graph -->
<meta property="og:type" content="article">
<meta property="og:title" content="Crema de calabaza asada: receta fácil">
<meta property="og:description" content="Suave, cremosa y lista en 30 minutos.">
<meta property="og:image" content="https://cocinadeotono.es/img/og/crema-calabaza.jpg">
<meta property="og:url" content="https://cocinadeotono.es/blog/crema-calabaza">
<meta property="og:locale" content="es_ES">
<meta name="twitter:card" content="summary_large_image">
<link rel="stylesheet" href="../css/estilos.css">
<!-- Datos estructurados -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": "Crema de calabaza asada: receta fácil en 30 minutos",
"image": "https://cocinadeotono.es/img/og/crema-calabaza.jpg",
"datePublished": "2026-10-03T09:00:00+02:00",
"dateModified": "2026-10-05T18:30:00+02:00",
"inLanguage": "es-ES",
"author": {
"@type": "Person",
"name": "Marta Sanz",
"url": "https://cocinadeotono.es/autores/marta-sanz"
},
"publisher": {
"@type": "Organization",
"name": "Cocina de Otoño",
"logo": { "@type": "ImageObject", "url": "https://cocinadeotono.es/img/logo.png" }
}
}
</script>
</head>
<body>
<a href="#contenido" class="saltar">Saltar al contenido principal</a>
<!-- ===== CABECERA DEL SITIO ===== -->
<header>
<a href="/" class="logo">
<img src="../img/logo.svg" alt="Cocina de Otoño: inicio" width="180" height="48">
</a>
<nav aria-label="Principal">
<ul>
<li><a href="/">Inicio</a></li>
<li><a href="/blog" aria-current="page">Blog</a></li>
<li><a href="/recetas">Recetas</a></li>
<li><a href="/contacto">Contacto</a></li>
</ul>
</nav>
</header>
<!-- ===== CONTENIDO PRINCIPAL ===== -->
<main id="contenido" tabindex="-1">
<nav aria-label="Migas de pan">
<ol>
<li><a href="/">Inicio</a></li>
<li><a href="/blog">Blog</a></li>
<li><a href="/blog/crema-calabaza" aria-current="page">Crema de calabaza asada</a></li>
</ol>
</nav>
<article>
<header>
<hgroup>
<h1>Crema de calabaza asada</h1>
<p>Receta fácil en 30 minutos, apta para veganos</p>
</hgroup>
<p>
Por <a href="/autores/marta-sanz" rel="author">Marta Sanz</a> ·
<time datetime="2026-10-03">3 de octubre de 2026</time> ·
Lectura de 4 minutos
</p>
</header>
<figure>
<img src="../img/crema-calabaza.webp"
alt="Cuenco de crema de calabaza de color naranja intenso con pipas tostadas y un hilo de aceite"
width="1200" height="800" fetchpriority="high">
<figcaption>La crema, recién hecha, con pipas de calabaza tostadas.</figcaption>
</figure>
<p>
Cuando llega el otoño, pocas cosas reconfortan tanto como una
<strong>crema caliente</strong>. Asar la calabaza antes de triturarla
es el secreto: <em>carameliza</em> sus azúcares y multiplica el sabor.
</p>
<section aria-labelledby="ingredientes">
<h2 id="ingredientes">Ingredientes (4 personas)</h2>
<ul>
<li>1 kg de calabaza</li>
<li>1 cebolla</li>
<li>2 dientes de ajo</li>
<li>750 ml de caldo de verduras</li>
<li>Aceite de oliva virgen extra</li>
<li>Sal, pimienta y nuez moscada</li>
</ul>
</section>
<section aria-labelledby="preparacion">
<h2 id="preparacion">Preparación</h2>
<ol>
<li>Precalienta el horno a 200 °C.</li>
<li>Asa la calabaza en dados con la cebolla y el ajo durante <time datetime="PT25M">25 minutos</time>.</li>
<li>Tritura todo con el caldo caliente hasta obtener una textura fina.</li>
<li>Salpimienta y añade una pizca de nuez moscada.</li>
</ol>
</section>
<footer>
<p>
Etiquetas:
<a href="/etiquetas/otono" rel="tag">otoño</a>,
<a href="/etiquetas/vegano" rel="tag">vegano</a>,
<a href="/etiquetas/cremas" rel="tag">cremas</a>
</p>
<address>
¿Dudas sobre la receta? Escribe a
<a href="mailto:marta@cocinadeotono.es">marta@cocinadeotono.es</a>
</address>
</footer>
<section aria-labelledby="comentarios">
<h2 id="comentarios">Comentarios (1)</h2>
<article>
<header>
<h3>Luis</h3>
<p><time datetime="2026-10-04T10:15">4 de octubre a las 10:15</time></p>
</header>
<p>La he hecho con boniato y también queda espectacular.</p>
</article>
</section>
</article>
<!-- ===== BARRA LATERAL ===== -->
<aside aria-labelledby="titulo-relacionadas">
<h2 id="titulo-relacionadas">Recetas relacionadas</h2>
<ul>
<li><a href="/blog/sopa-castanas">Sopa de castañas</a></li>
<li><a href="/blog/pure-boniato">Puré de boniato</a></li>
<li><a href="/blog/crema-setas">Crema de setas</a></li>
</ul>
</aside>
</main>
<!-- ===== PIE DEL SITIO ===== -->
<footer>
<nav aria-label="Pie">
<ul>
<li><a href="/aviso-legal">Aviso legal</a></li>
<li><a href="/privacidad">Política de privacidad</a></li>
<li><a href="/cookies">Cookies</a></li>
<li><a href="/accesibilidad">Declaración de accesibilidad</a></li>
</ul>
</nav>
<p><small>© 2026 Cocina de Otoño. Textos bajo licencia CC BY-SA 4.0.</small></p>
</footer>
</body>
</html>
Análisis del esquema
Regiones (landmarks):
├── banner ← header del body
│ └── navigation "Principal"
├── main
│ ├── navigation "Migas de pan"
│ ├── region "Ingredientes (4 personas)" ← section con aria-labelledby
│ ├── region "Preparación"
│ ├── region "Comentarios (1)"
│ └── complementary "Recetas relacionadas" ← aside
└── contentinfo ← footer del body
└── navigation "Pie"
Encabezados:
h1 Crema de calabaza asada
h2 Ingredientes (4 personas)
h2 Preparación
h2 Comentarios (1)
h3 Luis
h2 Recetas relacionadas
💡 Consejo: Valida esta página en el W3C, pásale Lighthouse (Accesibilidad, Buenas prácticas y SEO deberían rondar el 100) y recórrela con un lector de pantalla saltando por regiones y encabezados. Es la mejor forma de comprobar que la estructura funciona.
10. Errores comunes y su solución
| Error | Problema | Solución |
|---|---|---|
Todo con div (“divitis”) | Sin regiones ni significado para lectores de pantalla y buscadores | Elementos semánticos y div solo para maquetar |
section como contenedor genérico | Secciones sin título; esquema confuso | div si no hay encabezado con sentido |
aria-hidden="true" en elementos enfocables | “Fantasmas” para el lector de pantalla | Nunca en enlaces, botones ni sus contenedores |
outline: none sin alternativa | El usuario de teclado no sabe dónde está | Estilo de foco visible con :focus-visible |
Rutas relativas en og:image | Las redes sociales no muestran la imagen | URLs absolutas |
| JSON-LD con datos que no están en la página | Puede considerarse spam por los buscadores | Datos estructurados fieles al contenido visible |
11. Ejercicios
Ejercicio 1: De “sopa de divs” a HTML semántico
Reescribe este fragmento sustituyendo cada div por el elemento semántico adecuado (o dejándolo como div si no hay ninguno mejor), y justifica cada decisión en un comentario HTML:
<div class="top">
<div class="logo">TecnoBlog</div>
<div class="menu"><a href="/">Inicio</a> <a href="/moviles">Móviles</a> <a href="/portatiles">Portátiles</a></div>
</div>
<div class="wrapper">
<div class="post">
<div class="post-title">Análisis del nuevo portátil Ligero 13</div>
<div class="post-date">2 de octubre de 2026</div>
<div class="post-body">Texto del análisis...</div>
<div class="post-tags">Etiquetas: portátiles, análisis</div>
</div>
<div class="sidebar">Lo más leído: ...</div>
</div>
<div class="bottom">© 2026 TecnoBlog · contacto@tecnoblog.es</div>
Ejercicio 2: Portada de un periódico
Maqueta en HTML (sin CSS) la portada de un periódico digital con: cabecera con logo, fecha y menú de secciones; una noticia principal; una sección “Última hora” con 4 noticias breves; una sección “Opinión” con 3 columnas de opinión; una barra lateral con “Lo más leído”; y un pie con enlaces legales. Elige con criterio entre article, section, aside y div, y comprueba la jerarquía de encabezados.
Ejercicio 3: Preguntas frecuentes con details
Crea una página de preguntas frecuentes de una tienda online con 3 categorías (Envíos, Pagos, Devoluciones), cada una en su section con h2, y al menos 3 preguntas por categoría con details y summary. Haz que dentro de cada categoría solo pueda haber una respuesta abierta a la vez.
Ejercicio 4: Diálogo de confirmación
Crea una página con un botón “Eliminar mi cuenta” que abra un dialog modal con un título, un texto de advertencia y dos botones: “Cancelar” y “Sí, eliminar”, dentro de un form method="dialog". Comprueba que se cierra con Esc, que el foco queda atrapado dentro mientras está abierto y que vuelve al botón al cerrarse.
Ejercicio 5: Auditoría de accesibilidad
Elige una web real (un comercio local, la web de una asociación…) y analízala: recórrela solo con el teclado (¿se ve siempre el foco? ¿hay enlace para saltar al contenido?), revisa en DevTools → Accessibility si usa regiones (banner, navigation, main, contentinfo), comprueba la jerarquía de encabezados con la extensión HeadingsMap y el contraste de 3 textos con DevTools, y pásale Lighthouse y la extensión WAVE. Redacta un informe con 5 problemas encontrados y cómo los solucionarías.
Ejercicio 6: SEO y redes sociales
Toma la página del ejercicio 2 y añádele: title y meta description optimizados, URL canónica, etiquetas Open Graph completas (con una imagen de 1200 × 630), twitter:card, favicon en ICO y SVG y apple-touch-icon. Después añade un bloque JSON-LD de tipo NewsArticle para la noticia principal y valídalo en validator.schema.org.
Ejercicio 7: Tu blog semántico completo
Crea un pequeño blog personal de 3 páginas: portada (con un listado de 3 article con resumen y enlace “Leer más sobre…”), una entrada completa (con header, figure, varias section, comentarios como article anidados y footer) y una página “Sobre mí” con tu contacto en address. Las tres páginas deben compartir cabecera, navegación y pie, tener title y meta description únicos, Open Graph, JSON-LD adecuado y pasar el validador del W3C sin errores. Este blog te servirá para practicar CSS en los próximos módulos.
Resumen
| Concepto | Sintaxis / idea clave |
|---|---|
| Semántica | Cada etiqueta según su significado; div como último recurso |
header | Contenido introductorio de la página o de una sección |
nav | Navegación principal; varios nav con aria-label |
main | Contenido principal; solo uno visible por página |
article | Contenido autónomo (tendría sentido fuera de la página) |
section | Bloque temático con su propio encabezado |
aside | Contenido complementario (barra lateral, notas) |
footer | Información sobre la sección o la página |
search | Zona de búsqueda o filtrado |
hgroup | Encabezado + subtítulo en p |
address | Contacto del autor o propietario |
details / summary | Desplegable nativo; name para acordeón exclusivo |
dialog | Ventana de diálogo; showModal() para modal |
| Regiones | banner, navigation, main, complementary, contentinfo, search, region |
| WCAG 2.2 AA | Contraste 4,5 : 1 (texto normal), 3 : 1 (texto grande e interfaz) |
| Teclado | Elementos nativos, foco visible, orden lógico, nunca tabindex positivo |
| Saltar al contenido | <a href="#contenido"> + <main id="contenido" tabindex="-1"> |
| Primera regla de ARIA | Si existe un elemento HTML nativo, úsalo en lugar de ARIA |
| ARIA útil | aria-label, aria-labelledby, aria-describedby, aria-hidden, aria-current, aria-expanded, aria-live |
| SEO básico | title único, meta name="description", link rel="canonical", encabezados, alt |
| Open Graph | <meta property="og:title" content="...">, imagen absoluta 1200 × 630 |
| Favicon | favicon.ico + icon.svg + apple-touch-icon.png |
| JSON-LD | <script type="application/ld+json"> con vocabulario Schema.org |