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, aside y footer
  • Saber elegir entre article, section y div
  • Conocer otros elementos semánticos: address, details/summary, dialog, figure, time, mark y search
  • 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. El div es 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 header en una página (uno general y uno por artículo).
  • No puede ir dentro de otro header, de un footer ni de un address.
  • header no es lo mismo que head: head contiene metadatos invisibles; header es contenido visible del body.

<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. Reserva nav para la navegación importante. Y si hay varios nav en la página, distínguelos con aria-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: section no es un div con otro nombre. Si solo necesitas un contenedor para aplicar estilos (por ejemplo, para centrar o crear una columna), usa div. La regla práctica: si no puedes ponerle un encabezado con sentido, probablemente no es una section.

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 article puede contener section (los apartados de una entrada larga) y una section puede contener article (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> 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: address no 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. address es 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: details es accesible por defecto: se abre con el teclado (Intro o Espacio sobre el summary) 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> 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: mark no es para “destacar porque es importante” (eso es strong), 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 color y 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 outline sin sustituirlo.
  • Nunca uses tabindex con 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 Tab nada más cargar. ¿Aparece un enlace “Saltar al contenido” o similar? Sigue pulsando Tab y 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, no name. Y las URLs (sobre todo la de la imagen) deben ser absolutas: https://..., nunca img/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, un icon.svg y un apple-touch-icon.png cubres 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 script en el head o en el body.

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&nbsp;°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>&copy; 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