Ya sabes marcar texto, poner enlaces, incrustar imágenes y construir listas y tablas. Con eso tienes el vocabulario básico. Ahora viene la pregunta de fondo: ¿qué significa cada pieza?
El HTML que escribes tiene dos públicos a la vez. Uno son las personas, que lo ven a través
del navegador. El otro son las máquinas: el buscador que decide si apareces en Google, el
lector de pantalla de quien no ve la pantalla, y tu yo de dentro de seis meses intentando
entender el código. El HTML semántico habla a los dos. Un montón de <div> solo habla al primero,
y a medias.
Empezamos a construir el Overwatch Team Builder, el hilo conductor del curso. Su primera pieza es esta página: una cabecera, un grid de héroes y un pie.
Semántico significa “con significado”, no “con aspecto”#
Esta es la idea que lo desbloquea todo: la etiqueta no decide cómo se ve algo, decide qué es.
El aspecto lo pone el CSS. Por eso <div> y <span> son etiquetas neutras: cajas sin
significado, útiles solo para agarrar algo desde el CSS o el JavaScript.
Frente a ellas, hay etiquetas que describen su contenido: <header>, <nav>, <main>,
<article>, <section>, <aside>, <footer>. Cambiar un <div> por un <article> no mueve
un solo píxel, pero cambia lo que la página es para todo lo que no son ojos humanos.
El esqueleto: los landmarks#
Casi cualquier página se organiza en unas pocas regiones grandes, los landmarks:
<body>
<!-- cabecera: logo, navegación -->
<header>...</header>
<!-- navegación principal -->
<nav>...</nav>
<!-- el contenido único de esta página (solo uno) -->
<main>...</main>
<!-- pie: avisos, enlaces secundarios -->
<footer>...</footer>
</body>No son decorativos. Quien usa un lector de pantalla puede saltar directamente a cualquiera de
ellos: ir al menú, o ir al contenido principal sin escuchar antes toda la cabecera. Con <div> no
hay regiones a las que saltar, solo una sopa por la que avanzar a ciegas. Regla práctica: un único
<main> por página, con lo que de verdad distingue a esa página de las demás.
Un h1, y una jerarquía que tenga sentido#
Ya viste esto en el capítulo anterior: los encabezados son el índice del documento, no una
herramienta de tamaño. Lo importante aquí es aplicarlo a una página real con varias secciones:
un único <h1> para el título de la página, <h2> para cada sección principal y <h3> para
los elementos dentro de cada sección (el nombre de cada héroe, en nuestro caso).
article, section o div: cuál y cuándo#
Las tres se parecen, pero responden a preguntas distintas:
<article>— contenido autónomo, que se entendería fuera de su contexto: una carta de héroe, un comentario, una noticia. Si lo pudieras copiar a otra página y seguiría teniendo sentido, es unarticle.<section>— una agrupación temática con su propio encabezado: la sección “Plantilla” que envuelve el grid.<div>— una caja sin significado, solo para enganchar estilos o layout. Es perfecta para eso; el error es usarla también para lo que sí tiene nombre.
Detalles pequeños que cambian mucho#
langen el<html>(<html lang="es">): le dice al navegador y al lector en qué idioma está, para pronunciarlo bien.alten las imágenes: si la imagen informa, descríbela (alt="Gráfico de winrate por rol"). Si es decorativa y su info ya está en texto, déjala muda conalt=""para no repetir ruido —el atributo vacío es la forma correcta de marcar una imagen decorativa;aria-hidden="true"se reserva para ocultar elementos que no son imágenes, como un icono hecho con un<span>—.- Una colección de elementos iguales es una lista (
<ul>/<li>), no una pila de divs.
Nombrar regiones: aria-label y aria-labelledby#
Los landmarks ya dan estructura, pero cuando hay dos del mismo tipo —por
ejemplo dos <nav>, uno principal y otro de pie— el lector de pantalla los
anuncia igual («navegación», «navegación») y no se distinguen. Para darles nombre
hay dos atributos:
aria-labelescribe el nombre a mano:<nav aria-label="Principal">se anuncia como «navegación Principal». Úsalo cuando no hay un texto visible que sirva de nombre.aria-labelledbytoma el nombre de otro elemento de la página, por suid(el mismoidque viste en los enlaces de ancla). Sirve para atar una región a su propio título:
<!-- aria-labelledby apunta al id del encabezado que da nombre a esta región -->
<section aria-labelledby="titulo-plantilla">
<!-- el id sirve de ancla para el aria-labelledby de arriba -->
<h2 id="titulo-plantilla">Plantilla</h2>
...
</section>La región se anuncia con el texto del <h2>. La ventaja sobre aria-label:
reutiliza un texto que ya está visible, así que si cambias el título, el nombre
accesible cambia con él. Regla práctica: si ya hay un texto visible que nombra la
región, aria-labelledby; si no lo hay, aria-label.
Y la otra mitad: cómo organizas los ficheros#
La semántica ordena el HTML por dentro; la estructura de carpetas ordena el proyecto por fuera. El antipatrón es tenerlo todo tirado en la raíz. En cuanto el Team Builder crezca a un par de páginas con sus imágenes y scripts, acabas así:
team-builder/
├── index.html
├── perfil.html
├── styles.css
├── perfil.css
├── app.js
├── logo.svg
├── tracer.png
├── reinhardt.png
└── mercy.png ← y esto no para de crecerNadie encuentra nada. La regla sana es que la carpeta cuente de un vistazo qué hay y dónde. En un proyecto pequeño basta con separar por tipo:
team-builder/
├── index.html
├── perfil.html
├── css/
│ ├── styles.css
│ └── perfil.css
├── js/
│ └── app.js
└── assets/
├── logo.svg
└── heroes/
├── tracer.png
├── reinhardt.png
└── mercy.pngFíjate en que el ejercicio de este capítulo ya da el primer paso: el HTML en index.html y los
estilos aparte en styles.css, no todo mezclado. En proyectos grandes se va más lejos y se agrupa
por funcionalidad —todo lo del “perfil de héroe” (su HTML, su CSS, su JS) en la misma carpeta—,
que es el mismo instinto que la semántica: dar a cada cosa un sitio que explique qué es. Lo
retomaremos a fondo en la arquitectura del Nivel 7.
Pruébalo#
El playground incluye un fichero
styles.css. No tienes que entenderlo ni tocarlo: el CSS lo verás a partir del capítulo «Qué es CSS». Aquí solo importa la estructura HTML.
Esta carta de héroe está bien construida por dentro. Mira el código de la izquierda: el <article>,
el encabezado, la lista de descripción para las estadísticas. Cambia <article> por <div> y
<h3> por <span> y comprueba que el resultado se ve idéntico: esa es justo la trampa de la
semántica, que no se ve, pero está.
Comprueba lo que sabes#
Pregunta 1 de 5
Cambias un `<div>` por un `<article>` sin tocar el CSS. ¿Qué cambia en la página?
Tu turno#
Te toca maquetar la página entera, aquí mismo. El CSS ya está dado y estiliza por clases, así que el reto no es que se vea bien: es que esté bien construida. Edita el HTML en el playground; cuando creas que lo tienes, despliega las soluciones y compáralas con la tuya.
Ejercicio · en esta página
Maqueta la página del Team Builder
Construye en HTML la página principal del Team Builder: una cabecera con su navegación, un grid con una carta por héroe y un pie. El CSS ya está dado y estiliza por clases, así que tu página se verá bien la resuelvas como la resuelvas. Lo que se evalúa es la estructura: que la página signifique algo, no solo que se vea.
Paso 1: Que funcione
- La página muestra la cabecera, los tres héroes y el pie.
- Se ve correctamente (vale resolverla a base de div y span).
- Los enlaces de la navegación están presentes.
Paso 2: Que esté pulido
- Usas landmarks: header, nav, main y footer.
- Jerarquía de encabezados con sentido: un h1, un h2 de sección, un h3 por héroe.
- El grid es una lista (ul/li) y cada carta un article.
- Las estadísticas son una lista de descripción (dl) y el retrato decorativo va con aria-hidden.
Paso 3: Que sea excelente
- Enlace para saltar al contenido y sección atada a su título con aria-labelledby.
- El nav lleva aria-label, pensando en que haya más de una navegación.
- Un lector de pantalla recorre la página sin fricción.
- Sabes cómo separarías el proyecto en carpetas (HTML, estilos, assets) cuando crezca.
Ver soluciones
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Overwatch Team Builder — Plantilla</title>
<link rel="stylesheet" href="../../starter/styles.css" />
</head>
<body>
<!--
============================================================================
NIVEL OK — "funciona y se ve"
============================================================================
Todo resuelto con <div> y <span>. Renderiza EXACTAMENTE igual que los otros
niveles, porque el CSS estiliza por clases. Y ese es el punto: a la vista no
se distingue, pero la página no significa nada para una máquina.
Sus límites (los que arregla el nivel "Mejor"):
- Ni un solo landmark: un lector de pantalla no puede saltar a "el menú" o
"el contenido principal"; solo ve una maraña de divs anidados.
- Ningún encabezado real. El nombre de la app y los de los héroes son
<div>: el buscador no sabe cuál es el título de la página, y quien navega
por encabezados (algo habitual con lector de pantalla) no tiene nada.
- El grid no es una lista, así que la tecnología asistiva no anuncia
"3 héroes": solo lee tres bloques sueltos.
- El retrato decorativo no está marcado como tal.
Aun así: se ve y se lee con el ratón. Eso vale como OK.
-->
<div class="page">
<div class="site-header">
<div class="brand">Overwatch <span>Team Builder</span></div>
<div class="site-nav">
<a href="#">Héroes</a>
<a href="#">Equipos</a>
<a href="#">Estadísticas</a>
</div>
</div>
<div>
<div class="section-title">Plantilla</div>
<div class="hero-grid">
<div class="hero-card">
<div class="hero-top">
<div class="hero-portrait">TR</div>
<div>
<div class="hero-name">Tracer</div>
<div class="hero-role">Daño</div>
</div>
</div>
<div class="hero-stats">
<div class="stat">
<div class="stat-label">Partidas</div>
<div class="stat-value">120</div>
</div>
<div class="stat">
<div class="stat-label">Victorias</div>
<div class="stat-value">78</div>
</div>
<div class="stat">
<div class="stat-label">Winrate</div>
<div class="stat-value">65%</div>
</div>
</div>
</div>
<div class="hero-card">
<div class="hero-top">
<div class="hero-portrait">RE</div>
<div>
<div class="hero-name">Reinhardt</div>
<div class="hero-role">Tanque</div>
</div>
</div>
<div class="hero-stats">
<div class="stat">
<div class="stat-label">Partidas</div>
<div class="stat-value">90</div>
</div>
<div class="stat">
<div class="stat-label">Victorias</div>
<div class="stat-value">51</div>
</div>
<div class="stat">
<div class="stat-label">Winrate</div>
<div class="stat-value">57%</div>
</div>
</div>
</div>
<div class="hero-card">
<div class="hero-top">
<div class="hero-portrait">ME</div>
<div>
<div class="hero-name">Mercy</div>
<div class="hero-role">Apoyo</div>
</div>
</div>
<div class="hero-stats">
<div class="stat">
<div class="stat-label">Partidas</div>
<div class="stat-value">200</div>
</div>
<div class="stat">
<div class="stat-label">Victorias</div>
<div class="stat-value">130</div>
</div>
<div class="stat">
<div class="stat-label">Winrate</div>
<div class="stat-value">65%</div>
</div>
</div>
</div>
</div>
</div>
<div class="site-footer">Overwatch Team Builder — proyecto del curso.</div>
</div>
</body>
</html> Por qué este nivel
- Resuelve la página entera con div y span: se ve perfecta y se lee con el ratón.
- Funcionar y verse es el primer requisito, y lo cumple.
- Sus límites: ni un landmark, ningún encabezado real y el grid no es una lista. Para una máquina (buscador, lector de pantalla) la página es una maraña sin estructura.
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Overwatch Team Builder — Plantilla</title>
<link rel="stylesheet" href="../../starter/styles.css" />
</head>
<body>
<!--
============================================================================
NIVEL MEJOR — "pulido"
============================================================================
Mismo aspecto que el nivel OK, pero ahora la página SIGNIFICA algo. Cada
etiqueta describe su contenido, no solo lo coloca.
Por qué mejora al nivel OK:
- Landmarks reales: <header>, <main> y <footer>. Un lector de pantalla
ofrece saltar directamente a cada región.
- Jerarquía de encabezados con sentido: un único <h1> (la app), un <h2>
para la sección y un <h3> por héroe. Es el índice de la página.
- El menú es un <nav>: se anuncia como navegación, no como texto suelto.
- El grid es una LISTA (<ul>/<li>): la tecnología asistiva anuncia
"lista, 3 elementos". Cada carta es un <article>: contenido autónomo que
se entiende por sí solo.
- Las estadísticas son una lista de descripción (<dl>): cada dato es un
par etiqueta→valor explícito, no dos divs que solo el ojo relaciona.
- El retrato es decorativo (el nombre ya está en texto), así que se oculta
a la tecnología asistiva con aria-hidden.
Lo que aún pule el nivel Excelente: navegar con teclado sin recorrer todo el
menú, atar la sección a su título, y el elemento exacto para una medida.
-->
<div class="page">
<header class="site-header">
<h1 class="brand">Overwatch <span>Team Builder</span></h1>
<nav class="site-nav">
<a href="#">Héroes</a>
<a href="#">Equipos</a>
<a href="#">Estadísticas</a>
</nav>
</header>
<main>
<h2 class="section-title">Plantilla</h2>
<ul class="hero-grid">
<li>
<article class="hero-card">
<div class="hero-top">
<div class="hero-portrait" aria-hidden="true">TR</div>
<div>
<h3 class="hero-name">Tracer</h3>
<p class="hero-role">Daño</p>
</div>
</div>
<dl class="hero-stats">
<div class="stat"><dt>Partidas</dt><dd>120</dd></div>
<div class="stat"><dt>Victorias</dt><dd>78</dd></div>
<div class="stat"><dt>Winrate</dt><dd>65%</dd></div>
</dl>
</article>
</li>
<li>
<article class="hero-card">
<div class="hero-top">
<div class="hero-portrait" aria-hidden="true">RE</div>
<div>
<h3 class="hero-name">Reinhardt</h3>
<p class="hero-role">Tanque</p>
</div>
</div>
<dl class="hero-stats">
<div class="stat"><dt>Partidas</dt><dd>90</dd></div>
<div class="stat"><dt>Victorias</dt><dd>51</dd></div>
<div class="stat"><dt>Winrate</dt><dd>57%</dd></div>
</dl>
</article>
</li>
<li>
<article class="hero-card">
<div class="hero-top">
<div class="hero-portrait" aria-hidden="true">ME</div>
<div>
<h3 class="hero-name">Mercy</h3>
<p class="hero-role">Apoyo</p>
</div>
</div>
<dl class="hero-stats">
<div class="stat"><dt>Partidas</dt><dd>200</dd></div>
<div class="stat"><dt>Victorias</dt><dd>130</dd></div>
<div class="stat"><dt>Winrate</dt><dd>65%</dd></div>
</dl>
</article>
</li>
</ul>
</main>
<footer class="site-footer">Overwatch Team Builder — proyecto del curso.</footer>
</div>
</body>
</html> Por qué es mejor que el anterior
- Mismo aspecto, pero ahora la página significa algo: header, main y footer dan regiones que se pueden saltar.
- Un solo h1 y una jerarquía h1 → h2 → h3 que funciona como índice del documento.
- El grid es una lista (ul/li) y cada carta un article autónomo; las estadísticas, una lista de descripción (dl).
- El retrato decorativo se oculta a la tecnología asistiva con aria-hidden para no añadir ruido.
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Overwatch Team Builder — Plantilla</title>
<link rel="stylesheet" href="../../starter/styles.css" />
</head>
<body>
<!--
============================================================================
NIVEL EXCELENTE — "óptimo"
============================================================================
Parte del nivel Mejor (ya semántico) y lo lleva a que CUALQUIERA pueda usar
la página sin fricción, también con teclado y lector de pantalla.
Por qué mejora al nivel Mejor:
- Enlace para saltar al contenido (.skip-link): quien navega con teclado no
tiene que tabular por todo el menú en cada página. Aparece solo al
enfocarlo; el CSS dado ya lo gestiona.
- La sección está ATADA a su título con aria-labelledby: la región deja de
ser "sección sin nombre" y se anuncia como "Plantilla".
- El <nav> lleva aria-label="Principal": si mañana añades otra navegación
(un pie con enlaces, por ejemplo), la tecnología asistiva las distingue.
Estructura del proyecto (lo otro que pedía el capítulo):
- Esto es UN fichero porque es un ejercicio. En un proyecto real separarías:
el HTML en su sitio, los estilos en /css o /styles, las imágenes en
/assets... no 490 ficheros en la raíz. La regla: que la carpeta cuente de
un vistazo qué hay y dónde.
- Detalle fino: para una MEDIDA como el winrate, el elemento más expresivo
sería <meter min="0" max="100" value="65">65%</meter>. Aquí lo dejamos como
texto para no pelear con el CSS dado, pero conviene conocerlo.
-->
<div class="page">
<a class="skip-link" href="#contenido">Saltar al contenido</a>
<header class="site-header">
<h1 class="brand">Overwatch <span>Team Builder</span></h1>
<nav class="site-nav" aria-label="Principal">
<a href="#">Héroes</a>
<a href="#">Equipos</a>
<a href="#">Estadísticas</a>
</nav>
</header>
<main id="contenido">
<section aria-labelledby="titulo-plantilla">
<h2 class="section-title" id="titulo-plantilla">Plantilla</h2>
<ul class="hero-grid">
<li>
<article class="hero-card">
<div class="hero-top">
<div class="hero-portrait" aria-hidden="true">TR</div>
<div>
<h3 class="hero-name">Tracer</h3>
<p class="hero-role">Daño</p>
</div>
</div>
<dl class="hero-stats">
<div class="stat"><dt>Partidas</dt><dd>120</dd></div>
<div class="stat"><dt>Victorias</dt><dd>78</dd></div>
<div class="stat"><dt>Winrate</dt><dd>65%</dd></div>
</dl>
</article>
</li>
<li>
<article class="hero-card">
<div class="hero-top">
<div class="hero-portrait" aria-hidden="true">RE</div>
<div>
<h3 class="hero-name">Reinhardt</h3>
<p class="hero-role">Tanque</p>
</div>
</div>
<dl class="hero-stats">
<div class="stat"><dt>Partidas</dt><dd>90</dd></div>
<div class="stat"><dt>Victorias</dt><dd>51</dd></div>
<div class="stat"><dt>Winrate</dt><dd>57%</dd></div>
</dl>
</article>
</li>
<li>
<article class="hero-card">
<div class="hero-top">
<div class="hero-portrait" aria-hidden="true">ME</div>
<div>
<h3 class="hero-name">Mercy</h3>
<p class="hero-role">Apoyo</p>
</div>
</div>
<dl class="hero-stats">
<div class="stat"><dt>Partidas</dt><dd>200</dd></div>
<div class="stat"><dt>Victorias</dt><dd>130</dd></div>
<div class="stat"><dt>Winrate</dt><dd>65%</dd></div>
</dl>
</article>
</li>
</ul>
</section>
</main>
<footer class="site-footer">Overwatch Team Builder — proyecto del curso.</footer>
</div>
</body>
</html> Por qué es mejor que el anterior
- Parte de lo semántico y lo hace usable por cualquiera: un enlace para saltar el menú con el teclado.
- La sección se ata a su título con aria-labelledby, así la región tiene nombre; el nav lleva aria-label por si mañana hay otra.
- Conoce la estructura de carpetas de un proyecto real (separar HTML, estilos y assets). Sabe que un dato como el winrate tiene semántica propia más allá de un número en texto plano, aunque el elemento HTML que lo modela se ve más adelante en el curso.
- Misma pantalla que el nivel OK; un mundo de diferencia para quien no usa el ratón.