Texto, enlaces, imágenes, tablas
En resumen: El contenido de una página HTML se compone de encabezados h1–h6, párrafos, listas, enlaces con href, imágenes con alt y tablas de datos con caption, th y scope.
Problema
«Leche» no lleva a ninguna parte
Un cliente escribe que en la página del pan el enlace «Leche» lleva a «Página no encontrada». En la página de inicio el mismo menú funciona. La página está en https://shop.example/bread/white/dobrudzha.
Consola
Haz clic en «Leche». El sandbox es una sola página y no abre enlaces, pero la consola dice lo que hay en href: milk. Los otros enlaces empiezan con /, y este no. Sin / el navegador no va a /milk. Construye la dirección a partir de la dirección de la página en la que estás, y obtiene otra.
La idea
Direcciones completas y relativas
Un enlace es <a> con el atributo href: adónde lleva. La dirección de href (una URL) es completa o relativa.
Una dirección completa lo tiene todo: el esquema (https://), el host (shop.example) y la ruta (/bread/white/dobrudzha). La ruta se compone de carpetas separadas por /, y la última parte es la página.
Una dirección relativa está incompleta, y el navegador la completa con la dirección de la página en la que está el enlace. El principio decide cómo:
milk(nada delante): desde la carpeta de la página.../milk: una carpeta hacia arriba./milk: desde la raíz del sitio, sea cual sea la página.#price: un lugar dentro de la misma página, el elemento conid="price".
Por eso el menú, que está en todas las páginas, usa direcciones desde la raíz: /milk lleva al mismo sitio desde cualquier parte. Una dirección completa solo la escribes para otro sitio web.
Míralo
Mira cómo se construye la dirección
Cinco enlaces en la misma página. Fíjate en qué partes de la dirección de arriba conserva el navegador y qué añade.
<!-- la dirección de la página: https://shop.example/bread/white/dobrudzha --><a href="milk">Leche</a><a href="../milk">Leche</a><a href="/milk">Leche</a><a href="#price">El precio</a><a href="https://recipes.example/bread">Recetas</a>Paso a paso, en palabras
- La página está en https://shop.example/bread/white/dobrudzha. El navegador divide la dirección en partes: el esquema, el host y la ruta. La ruta está hecha de carpetas, y la última parte es la página misma.
- href="milk" no tiene nada delante. El navegador conserva la carpeta de la página, /bread/white/, y cambia solo la última parte. Obtiene /bread/white/milk: una página que no existe.
- ../ significa una carpeta hacia arriba. Desde /bread/white/ el navegador sube a /bread/ y añade milk. Sigue sin ser /milk.
- Una barra delante significa desde la raíz del sitio. El navegador conserva solo el esquema y el host. Esta dirección funciona desde cualquier página de la tienda.
- # es un lugar dentro de la misma página. El navegador conserva toda la dirección y añade #price. La página no se vuelve a cargar: se desplaza hasta el elemento con id="price".
- Una dirección con esquema es completa. El navegador no toma nada de la página. Así se enlaza a otro sitio.
- El principio de href decide: nada o ../ cuenta desde la carpeta, / desde la raíz, # se queda en la página, https:// es una dirección completa.
El navegador no le pregunta al servidor si la dirección existe. Construye la dirección según las reglas y va allí. Por eso un error en href no se nota en el código, sino solo cuando alguien hace clic en el enlace. En el navegador ves la dirección construida cuando pones el ratón sobre un enlace: abajo a la izquierda de la ventana.
Pruébalo
Arregla los enlaces
Arregla los enlaces: «Leche» y «Carrito» deben funcionar desde cualquier página de la tienda (una dirección desde la raíz: /milk y /cart). «Ir al precio» debe saltar al párrafo con el precio. «Recetas con este pan» lleva a otro sitio web: haz que se abra en una pestaña nueva sin que ese sitio obtenga acceso a nuestra página.
Consola
Solución
index.html<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<title>Pan Dobrudzha · La tienda</title>
</head>
<body>
<!-- La dirección de la página: https://shop.example/bread/white/dobrudzha -->
<nav>
<a href="/bread">Pan</a>
<a href="/milk">Leche</a>
<a href="/cart">Carrito</a>
</nav>
<main>
<h1>Pan Dobrudzha</h1>
<p><a href="#price">Ir al precio</a></p>
<p>Pan blanco, 650 g, horneado esta mañana.</p>
<p id="price">Precio: 1,80 €</p>
<p><a href="https://recipes.example/bread" target="_blank" rel="noopener">Recetas con este pan</a></p>
</main>
</body>
</html>
href="/milk" y href="/cart" empiezan desde la raíz y no dependen de la página. Aquí ../milk lleva a /bread/milk: una carpeta por encima de /bread/white/ es /bread/.
#price busca un elemento con id="price", así que el id va en el párrafo con el precio. La almohadilla está solo en el enlace, no en el id.
target="_blank" abre el enlace en una pestaña nueva. Entonces la página nueva puede llegar a la nuestra con window.opener y reemplazarla mientras el cliente lee recetas. rel="noopener" lo impide. Los navegadores nuevos lo hacen por sí solos con target="_blank", y rel lo dice de forma explícita y protege también en los más antiguos.
Pruébalo
Completa
El enlace tiene que saltar al precio de la misma página. ¿Qué va en href?
<a href="">Ir al precio</a>
…
<p id="price">Precio: 1,80 €</p>#price: una almohadilla y el id del elemento. El navegador no vuelve a cargar la página, se desplaza hasta el elemento.
La idea
Texto: esquema, párrafos, listas
Los encabezados <h1> … <h6> no son seis tamaños de letra. Son el esquema de la página, como el índice de un libro. <h1> es el título de la página, <h2> son sus secciones, <h3> son partes de una sección. Los niveles no se saltan: después de <h2> viene <h3>, no <h5>. Un lector de pantalla muestra este esquema como una lista, y la persona salta de sección en sección. El tamaño se cambia con CSS, sin tocar el esquema.
Lo demás:
<p>es un párrafo.<br>es un salto de línea dentro de un párrafo (una dirección, un poema), no una manera de hacer listas.<ul>es una lista sin orden (ingredientes),<ol>tiene orden (pasos). Cada elemento es<li>.<dl>es una lista de pares «nombre y valor»:<dt>es el nombre (Peso),<dd>es el valor (650 g).<strong>es importante («Contiene gluten.»), y<em>es un énfasis que cambia el significado («en una bolsa de tela, no en plástico»). Si solo quieres negrita por el aspecto, sin significado, eso es CSS.
Consola
Pruébalo
Haz un esquema
Un compañero eligió los encabezados por tamaño: el nombre del pan es un <h4> y la oferta es un <h1>. Haz el esquema: el nombre del producto es el título de la página, «Ingredientes» y «Cómo conservarlo» son las secciones. La oferta no es un encabezado: conviértela en texto importante dentro de un párrafo. Los ingredientes son una lista.
Consola
Solución
index.html<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<title>Pan Dobrudzha · La tienda</title>
</head>
<body>
<main>
<h1>Pan Dobrudzha</h1>
<p><strong>Oferta: −10 % hasta el domingo</strong></p>
<p>Pan blanco, 650 g, horneado esta mañana.</p>
<h2>Ingredientes</h2>
<ul>
<li>harina de trigo</li>
<li>agua</li>
<li>levadura</li>
<li>sal</li>
</ul>
<h2>Cómo conservarlo</h2>
<p>En una bolsa de tela, <em>no</em> en plástico.</p>
</main>
</body>
</html>
El nombre es <h1>, las secciones son <h2>, y la oferta pasa a ser <p><strong>…</strong></p>: es importante, pero no abre una sección. Los ingredientes son una <ul> con un <li> cada uno, sin orden, porque su orden no importa. La comprobación busca los encabezados por nivel y cuenta los elementos de la lista: los puntos y los <br> no son una lista, por mucho que se parezcan.
La idea
Imágenes
<img src="img/bread.svg" alt="Un pan blanco redondo con corteza crujiente" width="240" height="240">srces la dirección de la imagen. Se construye igual quehref.altes el texto en lugar de la imagen. Un lector de pantalla lo lee, un buscador lo indexa, y si la imagen no se carga, el navegador lo muestra en su lugar. Escribe lo que muestra la imagen y por qué está ahí.- Una imagen que es solo decoración, o que repite el texto de al lado, lleva
alt="". Entonces el lector de pantalla se la salta. No poneraltes un error: el lector no sabe que es decoración. - Una imagen que es lo único dentro de un enlace le da su nombre al enlace. El
altes adónde lleva el enlace: «La tienda», no «logotipo». widthyheightson el tamaño en píxeles. Mientras la imagen está en camino, el navegador no sabe cuánto mide y le da 0 píxeles de alto. Cuando llega, el texto de abajo salta hacia abajo. Conwidthyheightel navegador reserva el lugar de antemano, incluso cuando CSS la hace más pequeña.
Pruébalo
Añade alt y tamaños
El logotipo es lo único dentro del enlace a la página de inicio: el enlace tiene que llamarse «La tienda». La foto del pan muestra algo: descríbelo en alt. El icono de regalo repite el texto de al lado. La foto mide 240 × 240 píxeles: díselo al navegador.
Consola
Solución
index.html<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<title>Pan Dobrudzha · La tienda</title>
</head>
<body>
<header>
<a href="/"><img src="img/logo.svg" alt="La tienda" width="40" height="40"></a>
</header>
<main>
<h1>Pan Dobrudzha</h1>
<img src="img/bread.svg" alt="Un pan blanco redondo con corteza crujiente" width="240" height="240">
<p>Precio: 1,80 €</p>
<p><img src="img/gift.svg" alt="" width="24" height="24"> Regalo en pedidos de más de 30 €</p>
</main>
</body>
</html>
El logotipo lleva alt="La tienda": ese es el nombre del enlace. La foto lleva un alt que describe el pan, y width="240" height="240". El icono lleva alt="": el texto de al lado ya dice «regalo». La comprobación cuenta las imágenes por rol: una imagen con alt="" no tiene rol de «imagen», y una imagen sin alt sí, así que un alt vacío no es lo mismo que uno que falta.
Un lector de pantalla dice solo «enlace» en uno de los enlaces. ¿En qué línea está el error?
En la línea 5 el enlace al carrito contiene solo una imagen con alt="". Así que el enlace no tiene nombre. La imagen no es decoración: es lo único que dice adónde lleva el enlace. Necesita alt="Carrito".
La idea
Tablas de datos
Una tabla es para datos en filas y columnas: precios, pedidos, un horario. Un lector de pantalla la lee como tabla: «fila 3, columna Precio, 7,90 €». Por eso es solo para datos, y no para colocar una página en columnas. Eso lo hace CSS.
<table> <caption>Precios de hoy</caption> <thead> <tr><th scope="col">Producto</th><th scope="col">Precio</th></tr> </thead> <tbody> <tr><th scope="row">Queso blanco</th><td>7,90 €</td></tr> </tbody></table><caption>es el nombre de la tabla. Va dentro de la tabla, no como un encabezado encima.<tr>es una fila.<td>es una celda de datos, y<th>es una celda de encabezado.scope="col"dice que el encabezado es de la columna que tiene debajo, yscope="row"de la fila que tiene a la derecha.<thead>contiene las filas de encabezado,<tbody>los datos,<tfoot>la suma de abajo.
Pruébalo
La lista de precios
La lista de precios está hecha con celdas <td> y texto en negrita. Conviértela en una tabla de datos: el nombre «Precios de hoy» pertenece a la tabla, la fila de encabezado va en <thead>, y los productos van en <tbody>. Los encabezados de columna y los nombres de los productos son celdas de encabezado; usa scope para decir de qué es cada una.
Consola
Solución
index.html<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<title>Precios de hoy · La tienda</title>
</head>
<body>
<table>
<caption>Precios de hoy</caption>
<thead>
<tr><th scope="col">Producto</th><th scope="col">Precio</th><th scope="col">En stock</th></tr>
</thead>
<tbody>
<tr><th scope="row">Pan Dobrudzha</th><td>1,80 €</td><td>24</td></tr>
<tr><th scope="row">Leche fresca 3%</th><td>2,40 €</td><td>35</td></tr>
<tr><th scope="row">Queso blanco</th><td>7,90 €</td><td>18</td></tr>
<tr><th scope="row">Manzanas, 1 kg</th><td>2,10 €</td><td>80</td></tr>
</tbody>
</table>
</body>
</html>
<caption>Precios de hoy</caption> le da su nombre a la tabla: un lector de pantalla lo dice cuando llega a ella. La fila de encabezado va en <thead> con tres <th scope="col">, y cada nombre de producto es un <th scope="row">. Ahora la celda «7,90 €» tiene dos encabezados: «Queso blanco» y «Precio».
En una tabla simple como esta Chromium adivina los encabezados incluso sin scope, por dónde están. scope lo dice de forma explícita y no depende de adivinar: en una tabla con celdas combinadas la suposición puede fallar. La comprobación busca scope, así que un <th> sin él no pasa.
Más precisamente, para curiosos
Más sobre direcciones. ./milk es lo mismo que milk. ?page=2 conserva la ruta y cambia solo la consulta: /bread/white/dobrudzha?page=2. Una dirección que empieza con // conserva solo el esquema: //cdn.example/x.js es https://cdn.example/x.js. ../ por encima de la raíz no sube más: ../../../milk desde nuestra página es /milk. En JavaScript esta construcción es new URL('../milk', 'https://shop.example/bread/white/dobrudzha').
target="_blank" y window.opener. Chromium ya da a la pestaña nueva de target="_blank" window.opener === null, igual que con rel="noopener", y rel="opener" devuelve la conexión. Escribimos rel="noopener" para que quede claro también en los navegadores que no lo hacen por sí solos.
Imágenes para distintas pantallas. srcset da varios tamaños de una imagen, y el navegador elige uno para la pantalla. loading="lazy" carga la imagen cuando se acerca a la pantalla. Ambas cosas pertenecen al módulo de CSS y a la velocidad.
Celdas combinadas. colspan="3" combina tres celdas de una fila, y rowspan="2" dos celdas de una columna. Entonces scope importa: sin él el navegador adivina de qué celdas es el encabezado.
<b>, <i> y <br>. Existen y tienen un significado: <b> es texto que destaca sin ser más importante (una palabra clave), <i> es otra voz (un término, el título de un libro). Para texto importante existe <strong>, para el énfasis existe <em>.
Ejercicio
Pedido 1042
Construye desde cero la página del pedido 1042. Un menú con un enlace «Mis pedidos» a /orders. Un encabezado «Pedido 1042», y debajo «Estado: enviado». Después una tabla «Los productos del pedido» con las columnas Producto, Cantidad, Precio, Suma: Pan Dobrudzha 2 × 1,80 € = 3,60 €, Leche fresca 3% 1 × 2,40 € = 2,40 €, Queso blanco 1 × 7,90 € = 7,90 €. Delante de cada nombre hay una imagen de 32 × 32 (img/bread.svg, img/milk.svg, img/cheese.svg). Abajo va la fila «Total» con 13,90 € en la última columna.
Consola
Solución
index.html<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<title>Pedido 1042 · La tienda</title>
</head>
<body>
<nav>
<a href="/orders">Mis pedidos</a>
</nav>
<main>
<h1>Pedido 1042</h1>
<p>Estado: <strong>enviado</strong></p>
<table>
<caption>Los productos del pedido</caption>
<thead>
<tr>
<th scope="col">Producto</th>
<th scope="col">Cantidad</th>
<th scope="col">Precio</th>
<th scope="col">Suma</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row"><img src="img/bread.svg" alt="" width="32" height="32"> Pan Dobrudzha</th>
<td>2</td>
<td>1,80 €</td>
<td>3,60 €</td>
</tr>
<tr>
<th scope="row"><img src="img/milk.svg" alt="" width="32" height="32"> Leche fresca 3%</th>
<td>1</td>
<td>2,40 €</td>
<td>2,40 €</td>
</tr>
<tr>
<th scope="row"><img src="img/cheese.svg" alt="" width="32" height="32"> Queso blanco</th>
<td>1</td>
<td>7,90 €</td>
<td>7,90 €</td>
</tr>
</tbody>
<tfoot>
<tr>
<th scope="row" colspan="3">Total</th>
<td>13,90 €</td>
</tr>
</tfoot>
</table>
</main>
</body>
</html>
El nombre del producto es un <th scope="row">, y la imagen dentro lleva alt="": el nombre está al lado, así que la imagen es decoración. Con alt="Pan Dobrudzha" el encabezado de la fila sería «Pan Dobrudzha Pan Dobrudzha». «Total» va en <tfoot>: es la suma de la tabla, no un producto más. La celda «Total» es <th scope="row" colspan="3"> y ocupa las tres columnas de la izquierda.
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: El documento HTML
- Continúa en: Formularios · Accesibilidad · Diseño responsive · Eventos
- Ejercicios sobre el tema: HTML: la estructura de la página