Módulo 04: Tablas
Objetivos del módulo
- Entender qué son los datos tabulares y cuándo tiene sentido usar una tabla
- Construir tablas con
table,tr,tdyth - Dar título a una tabla con
caption - Organizar las filas en
thead,tbodyytfoot - Combinar celdas con
colspanyrowspan - Agrupar columnas con
colgroupycol - Hacer tablas accesibles con
scopeyheaders - Saber por qué no se deben usar tablas para maquetar
- Dar un estilo básico a las tablas con CSS (adelanto)
- Conseguir que las tablas funcionen en pantallas pequeñas
1. ¿Qué es una tabla y cuándo usarla?
📘 Concepto: Una tabla HTML representa datos tabulares: información organizada en filas y columnas en la que cada dato tiene sentido gracias a su encabezado de fila y/o su encabezado de columna. Es el equivalente en la web a una hoja de cálculo.
La pregunta clave para decidir si algo es una tabla es: ¿tiene sentido leer los datos por filas y por columnas?
| Contenido | ¿Tabla? | Por qué |
|---|---|---|
| Horario de clases (días × horas) | Sí | Cada celda se entiende por su día (columna) y su hora (fila) |
| Comparativa de tarifas de móvil | Sí | Cada dato depende de la tarifa y de la característica |
| Clasificación de una liga | Sí | Equipos en filas, estadísticas en columnas |
| Factura con conceptos, cantidades y precios | Sí | Datos estructurados con totales |
| Galería de 9 fotos en cuadrícula de 3 × 3 | No | Las fotos no tienen relación de fila y columna; usa CSS Grid |
| Diseño de página con cabecera, menú lateral y contenido | No | Es maquetación; usa elementos semánticos y CSS |
| Lista de ingredientes | No | Es una lista: ul |
| Formulario con etiquetas a la izquierda | No | Usa label, input y CSS |
💡 Consejo: Si dudas, imagina que se lo lees a alguien por teléfono. En una tabla real dirías cosas como “el martes, a las diez, hay matemáticas”: fila + columna = dato.
2. Estructura básica: table, tr, td y th
| Elemento | Significado |
|---|---|
<table> | La tabla completa |
<tr> | Table row: una fila |
<td> | Table data: una celda de datos |
<th> | Table header: una celda de encabezado |
💻 Ejemplo: Una tabla sencilla de precios:
<table>
<tr>
<th>Producto</th>
<th>Precio</th>
<th>Stock</th>
</tr>
<tr>
<td>Teclado mecánico</td>
<td>89,90 €</td>
<td>15</td>
</tr>
<tr>
<td>Ratón inalámbrico</td>
<td>34,50 €</td>
<td>42</td>
</tr>
<tr>
<td>Monitor 27"</td>
<td>279,00 €</td>
<td>7</td>
</tr>
</table>
Resultado esperado: una tabla de 4 filas y 3 columnas. La primera fila aparece en negrita y centrada (estilo por defecto de th). Sin CSS, la tabla no tiene bordes: los datos aparecen alineados en columnas.
📘 Concepto: Las tablas se construyen fila a fila: primero abres una fila (
tr) y dentro pones sus celdas (tdoth) de izquierda a derecha. No existe un elemento “columna” que contenga celdas; las columnas se forman solas al alinear las celdas de todas las filas.
flowchart TD
T[table] --> R1[tr]
T --> R2[tr]
T --> R3[tr]
R1 --> H1[th Producto]
R1 --> H2[th Precio]
R1 --> H3[th Stock]
R2 --> D1[td Teclado]
R2 --> D2["td 89,90 €"]
R2 --> D3[td 15]
R3 --> D4[td Ratón]
R3 --> D5["td 34,50 €"]
R3 --> D6[td 42]
⚠️ Importante: Todas las filas deben tener el mismo número de celdas (teniendo en cuenta las combinaciones que veremos en la sección 5). Si una fila tiene menos, la tabla queda descuadrada. El validador del W3C te avisará.
th en filas y en columnas
Los encabezados no solo van en la primera fila. También puede haber encabezados de fila, en la primera columna:
<table>
<tr>
<th></th>
<th>Lunes</th>
<th>Martes</th>
</tr>
<tr>
<th>Mañana</th>
<td>Yoga</td>
<td>Pilates</td>
</tr>
<tr>
<th>Tarde</th>
<td>Spinning</td>
<td>Natación</td>
</tr>
</table>
Aquí “Natación” se entiende como “martes por la tarde”: encabezado de columna + encabezado de fila.
💡 Consejo: La celda vacía de la esquina superior izquierda es habitual en tablas de doble entrada. Puede ser un
tdvacío o unthvacío; algunos autores prefierentdpara que los lectores de pantalla no anuncien un encabezado sin texto.
3. El título de la tabla: caption
📘 Concepto:
<caption>es el título de la tabla. Debe ser el primer hijo detable. Los lectores de pantalla lo anuncian al llegar a la tabla, lo que permite al usuario decidir si le interesa leerla o saltarla.
<table>
<caption>Tarifas de fibra óptica (precios con IVA, octubre de 2026)</caption>
<tr>
<th>Velocidad</th>
<th>Precio al mes</th>
<th>Permanencia</th>
</tr>
<tr>
<td>300 Mb</td>
<td>25 €</td>
<td>No</td>
</tr>
<tr>
<td>1 Gb</td>
<td>35 €</td>
<td>12 meses</td>
</tr>
</table>
Por defecto se muestra centrado encima de la tabla. Con CSS (caption-side: bottom) puedes moverlo debajo.
⚠️ Importante: Si la tabla está dentro de un
figure, puedes usarfigcaptionen lugar decaption, pero no los dos con el mismo texto. Y no uses unh2suelto encima de la tabla como sustituto delcaption: el encabezado no queda asociado a la tabla.
4. Grupos de filas: thead, tbody y tfoot
Igual que un documento tiene cabecera, cuerpo y pie, una tabla puede dividir sus filas en tres grupos:
| Elemento | Contiene | Ejemplo |
|---|---|---|
<thead> | La fila (o filas) de encabezados de columna | Producto, Cantidad, Precio |
<tbody> | Las filas de datos | Cada producto |
<tfoot> | Filas de resumen | Subtotal, IVA, Total |
💻 Ejemplo: Una factura:
<table>
<caption>Factura n.º 2026-0147</caption>
<thead>
<tr>
<th>Concepto</th>
<th>Cantidad</th>
<th>Precio unitario</th>
<th>Importe</th>
</tr>
</thead>
<tbody>
<tr>
<td>Diseño de logotipo</td>
<td>1</td>
<td>300,00 €</td>
<td>300,00 €</td>
</tr>
<tr>
<td>Página web (5 secciones)</td>
<td>1</td>
<td>900,00 €</td>
<td>900,00 €</td>
</tr>
<tr>
<td>Hora de formación</td>
<td>4</td>
<td>40,00 €</td>
<td>160,00 €</td>
</tr>
</tbody>
<tfoot>
<tr>
<th colspan="3">Base imponible</th>
<td>1.360,00 €</td>
</tr>
<tr>
<th colspan="3">IVA (21 %)</th>
<td>285,60 €</td>
</tr>
<tr>
<th colspan="3">Total</th>
<td><strong>1.645,60 €</strong></td>
</tr>
</tfoot>
</table>
¿Por qué usar estos grupos?
- Significado: el navegador, los lectores de pantalla y otras herramientas saben qué filas son encabezados y cuáles son resúmenes.
- Estilo: puedes dar estilo distinto a cada grupo (
thead { background: ... }) sin añadir clases. - Impresión: al imprimir una tabla larga que ocupa varias páginas, los navegadores pueden repetir
theadytfooten cada página. - Desplazamiento: con CSS puedes dejar fija la cabecera mientras se desplaza el cuerpo.
💡 Consejo: Puedes usar varios
tbodyen una misma tabla para agrupar filas relacionadas (por ejemplo, untbodypor mes o por categoría de productos).
📘 Concepto: Si escribes filas directamente dentro de
tablesintbody, el navegador crea untbodyautomáticamente en el DOM. Compruébalo en el panel Elements de DevTools con la tabla de la sección 2. Por eso, al escribir CSS comotable > tr, el selector no funciona: en el DOM, las filas son hijas detbody.
5. Combinar celdas: colspan y rowspan
| Atributo | Efecto | Ejemplo |
|---|---|---|
colspan="n" | La celda se extiende horizontalmente ocupando n columnas | Un título que abarca 3 columnas |
rowspan="n" | La celda se extiende verticalmente ocupando n filas | Una asignatura de dos horas seguidas |
colspan
<table>
<caption>Resultados de la evaluación</caption>
<thead>
<tr>
<th rowspan="2">Alumno</th>
<th colspan="3">Notas</th>
</tr>
<tr>
<th>1.º trimestre</th>
<th>2.º trimestre</th>
<th>3.º trimestre</th>
</tr>
</thead>
<tbody>
<tr>
<th>Ana Ruiz</th>
<td>8,5</td>
<td>9,0</td>
<td>9,2</td>
</tr>
<tr>
<th>Pablo Gil</th>
<td>6,0</td>
<td>7,5</td>
<td>7,0</td>
</tr>
</tbody>
</table>
Resultado esperado: la celda “Notas” ocupa el ancho de las tres columnas de trimestres, y “Alumno” ocupa la altura de las dos filas de encabezado.
rowspan: un horario
<table>
<caption>Horario de 1.º de DAW</caption>
<thead>
<tr>
<th>Hora</th>
<th>Lunes</th>
<th>Martes</th>
<th>Miércoles</th>
</tr>
</thead>
<tbody>
<tr>
<th>8:00</th>
<td rowspan="2">Programación</td>
<td>Bases de datos</td>
<td>Lenguajes de marcas</td>
</tr>
<tr>
<th>9:00</th>
<!-- La celda del lunes la ocupa "Programación" (rowspan) -->
<td>Sistemas</td>
<td rowspan="2">Entornos de desarrollo</td>
</tr>
<tr>
<th>10:00</th>
<td>Inglés</td>
<td>FOL</td>
<!-- La celda del miércoles la ocupa "Entornos de desarrollo" -->
</tr>
<tr>
<th>11:00</th>
<td colspan="3">Recreo</td>
</tr>
</tbody>
</table>
⚠️ Importante: La regla que más errores evita: cuando una celda ocupa varias filas con
rowspan, en las filas siguientes debes escribir una celda menos. Y cuando una celda ocupa varias columnas concolspan, esa fila tiene menos celdas. Si no lo haces, la tabla se descuadra y aparecen celdas “salientes” a la derecha.
Método para no perderse
- Dibuja la tabla en papel (o en una hoja de cálculo) con todas sus celdas.
- Marca las celdas combinadas.
- Escribe el HTML fila a fila. En cada fila, salta las posiciones que ya están ocupadas por un
rowspande una fila superior. - Cuenta: cada fila debe sumar el mismo número de columnas (cada celda cuenta 1, o su
colspan).
Cuadrícula del horario (4 columnas):
| Lunes | Martes | Miércoles
8:00 | Programación | BBDD | L. marcas
9:00 | (ocupada) | Sistemas | Entornos
10:00 | Inglés | FOL | (ocupada)
11:00 | Recreo ──────────────────────────
6. Grupos de columnas: colgroup y col
<colgroup> y <col> permiten aplicar ciertos estilos a columnas completas sin poner una clase en cada celda.
<table>
<caption>Comparativa de portátiles</caption>
<colgroup>
<col> <!-- 1.ª columna: nombre -->
<col span="2" class="columna-specs"> <!-- 2.ª y 3.ª: especificaciones -->
<col class="columna-precio"> <!-- 4.ª: precio -->
</colgroup>
<thead>
<tr>
<th>Modelo</th>
<th>RAM</th>
<th>Almacenamiento</th>
<th>Precio</th>
</tr>
</thead>
<tbody>
<tr>
<td>Ligero 13</td>
<td>16 GB</td>
<td>512 GB</td>
<td>899 €</td>
</tr>
<tr>
<td>Potente 16</td>
<td>32 GB</td>
<td>1 TB</td>
<td>1.599 €</td>
</tr>
</tbody>
</table>
/* Adelanto de CSS */
.columna-specs { background-color: #f1f5f9; }
.columna-precio { background-color: #fef9c3; width: 120px; }
colgroupva justo después delcaptiony antes dethead.- Cada
colrepresenta una columna;span="2"hace que uncolrepresente dos.
⚠️ Importante: A través de
colsolo funcionan unas pocas propiedades CSS:background,border(conborder-collapse: collapse),widthyvisibility: collapse. No puedes cambiar el color del texto, la alineación ni elpaddingde una columna concol. Para eso, usa selectores comotd:nth-child(4)(lo verás en el módulo 07).
7. Accesibilidad de las tablas
Una persona que ve la tabla relaciona cada dato con sus encabezados de un vistazo. Un usuario de lector de pantalla la recorre celda a celda, y el lector le anuncia los encabezados correspondientes, pero solo si la tabla está bien marcada.
💻 Ejemplo: Al llegar a la celda “Natación” de una tabla bien hecha, el lector de pantalla dirá algo como: “Martes, Tarde, Natación”. En una tabla mal marcada (todo con
td) dirá simplemente “Natación”, sin contexto.
El atributo scope
Indica a qué celdas se aplica un encabezado th:
| Valor | El th es encabezado de… |
|---|---|
col | Su columna |
row | Su fila |
colgroup | Un grupo de columnas (cuando el th usa colspan) |
rowgroup | Un grupo de filas (cuando el th usa rowspan o encabeza un tbody) |
<table>
<caption>Consumo eléctrico del hogar (kWh)</caption>
<thead>
<tr>
<td></td>
<th scope="col">Enero</th>
<th scope="col">Febrero</th>
<th scope="col">Marzo</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Cocina</th>
<td>120</td>
<td>110</td>
<td>105</td>
</tr>
<tr>
<th scope="row">Calefacción</th>
<td>340</td>
<td>290</td>
<td>180</td>
</tr>
</tbody>
</table>
💡 Consejo: En tablas simples (una fila de encabezados arriba), los navegadores deducen correctamente el
scope. Aun así, añadirlo siempre es una buena costumbre que no cuesta nada y evita ambigüedades, sobre todo cuando hay encabezados de fila.
Tablas complejas: headers e id
Cuando una tabla tiene varios niveles de encabezados y scope no basta, se puede indicar explícitamente qué encabezados corresponden a cada celda: das un id a cada th y en cada td enumeras esos ids en el atributo headers, separados por espacios.
<table>
<caption>Ventas por trimestre y región (miles de euros)</caption>
<thead>
<tr>
<td></td>
<th id="t1" scope="col">T1</th>
<th id="t2" scope="col">T2</th>
</tr>
</thead>
<tbody>
<tr>
<th id="norte" colspan="3" scope="colgroup">Zona norte</th>
</tr>
<tr>
<th id="galicia" headers="norte">Galicia</th>
<td headers="norte galicia t1">150</td>
<td headers="norte galicia t2">172</td>
</tr>
<tr>
<th id="asturias" headers="norte">Asturias</th>
<td headers="norte asturias t1">98</td>
<td headers="norte asturias t2">105</td>
</tr>
</tbody>
<tbody>
<tr>
<th id="sur" colspan="3" scope="colgroup">Zona sur</th>
</tr>
<tr>
<th id="andalucia" headers="sur">Andalucía</th>
<td headers="sur andalucia t1">310</td>
<td headers="sur andalucia t2">298</td>
</tr>
</tbody>
</table>
Al llegar a “172”, un lector de pantalla puede anunciar “Zona norte, Galicia, T2, 172”.
⚠️ Importante: Las tablas con
headersson laboriosas de escribir y mantener. Antes de recurrir a ellas, pregúntate si podrías dividir la tabla en varias más sencillas. Casi siempre es mejor solución, para todos los usuarios.
Lista de comprobación de accesibilidad
| Buena práctica | Por qué |
|---|---|
Usa caption | Identifica la tabla antes de entrar en ella |
Usa th para los encabezados, nunca un td en negrita | Los lectores de pantalla solo asocian los th |
Añade scope a los th | Deja claro si encabezan filas o columnas |
| Evita combinar celdas sin necesidad | colspan y rowspan complican la lectura celda a celda |
| No dejes celdas de datos vacías sin explicación | Usa “—”, “0” o “No disponible”, según el caso |
| Prefiere varias tablas simples a una muy compleja | Más fáciles de entender para todos |
| No uses imágenes de tablas | El texto de una imagen no es accesible ni buscable |
💡 Consejo: El atributo
summarydetable, que verás en tutoriales antiguos, está obsoleto en HTML5. Si la tabla necesita explicación, escríbela en elcaptiono en un párrafo cercano.
8. Cuándo NO usar tablas: la maquetación
Durante los años 90 y principios de los 2000, como CSS era muy limitado, las páginas web completas se maquetaban con tablas: una celda para la cabecera, otra para el menú, otra para el contenido… Era un truco, no el propósito de las tablas.
<!-- ❌ Maquetación con tablas: NO HAGAS ESTO -->
<table width="100%" border="0" cellpadding="0" cellspacing="0">
<tr>
<td colspan="2" bgcolor="#003366"><img src="logo.gif"></td>
</tr>
<tr>
<td width="200" valign="top">Menú...</td>
<td>Contenido...</td>
</tr>
</table>
Por qué es una mala idea
| Problema | Explicación |
|---|---|
| Accesibilidad | El lector de pantalla anuncia “tabla con 2 filas y 2 columnas” y lee el contenido como si fueran datos |
| Responsive | Una tabla no se puede reorganizar fácilmente para el móvil: las columnas siguen ahí |
| Mantenimiento | Cambiar el diseño exige reescribir el HTML entero |
| Semántica y SEO | El contenido queda enterrado en celdas sin significado |
| Rendimiento | Las tablas grandes son costosas de calcular para el navegador |
| HTML obsoleto | Atributos como width, bgcolor, cellpadding, cellspacing, align, valign o border están obsoletos y ya no son válidos en HTML5 |
⚠️ Importante: Para maquetar usa elementos semánticos (
header,nav,main,aside,footer, que verás en el módulo 06) y CSS (Flexbox en el módulo 10, Grid en el módulo 11, o la rejilla de Bootstrap en el módulo 15).
💡 Consejo: La única excepción que encontrarás hoy son los correos electrónicos en HTML (newsletters): muchos clientes de correo tienen un soporte de CSS muy pobre y siguen necesitando tablas para maquetar. En ese caso se añade
role="presentation"a la tabla para que los lectores de pantalla no la anuncien como tabla de datos. Es un mundo aparte: en una página web, nunca.
9. Estilo básico de tablas con CSS (adelanto)
Sin CSS, una tabla HTML es poco legible: sin bordes, sin separación y con las celdas muy juntas. Aunque el CSS empieza en el módulo 07, aquí tienes una hoja de estilos base que puedes reutilizar.
/* ===== Estilo base para tablas de datos ===== */
table {
border-collapse: collapse; /* Une los bordes de celdas contiguas en uno solo */
width: 100%; /* Ocupa todo el ancho disponible */
font-family: system-ui, sans-serif;
font-size: 0.95rem;
}
caption {
font-weight: bold;
font-size: 1.1rem;
text-align: left;
padding: 8px 0;
}
th,
td {
border: 1px solid #cbd5e1; /* Borde gris claro */
padding: 8px 12px; /* Espacio interior de las celdas */
text-align: left;
}
thead th {
background-color: #1e3a8a; /* Azul oscuro */
color: white;
}
/* Filas alternas ("cebra") para seguir las filas con la vista */
tbody tr:nth-child(even) {
background-color: #f1f5f9;
}
/* Resaltar la fila bajo el ratón */
tbody tr:hover {
background-color: #e0e7ff;
}
/* Números alineados a la derecha y con cifras del mismo ancho */
td.numero {
text-align: right;
font-variant-numeric: tabular-nums;
}
tfoot {
font-weight: bold;
background-color: #e2e8f0;
}
| Propiedad | Efecto |
|---|---|
border-collapse: collapse | Sin ella, cada celda tiene su propio borde y se ven líneas dobles |
padding en celdas | Sustituye al obsoleto cellpadding |
text-align | Alineación horizontal del contenido de las celdas |
vertical-align | Alineación vertical (top, middle, bottom) |
:nth-child(even) | Selecciona las filas pares (efecto cebra) |
font-variant-numeric: tabular-nums | Cifras de igual ancho: los números se alinean en columna |
caption-side: bottom | Coloca el título debajo de la tabla |
💡 Consejo: Alinea a la derecha las columnas numéricas (precios, cantidades) para que unidades, decenas y centenas queden en columna y se puedan comparar de un vistazo. El texto, a la izquierda.
Cabecera fija al desplazar
/* La cabecera se queda pegada arriba mientras se desplazan las filas */
thead th {
position: sticky;
top: 0;
}
10. Tablas responsive
Una tabla con 8 columnas no cabe en un móvil de 375 píxeles. Hay varias estrategias.
Estrategia 1: desplazamiento horizontal (la más sencilla y robusta)
Envuelve la tabla en un contenedor que permita desplazarse horizontalmente:
<div class="tabla-responsive" role="region" aria-labelledby="titulo-tarifas" tabindex="0">
<table>
<caption id="titulo-tarifas">Comparativa de tarifas móviles</caption>
<!-- ... muchas columnas ... -->
</table>
</div>
.tabla-responsive {
overflow-x: auto; /* Barra de desplazamiento horizontal solo si hace falta */
}
| Atributo del contenedor | Por qué |
|---|---|
tabindex="0" | Permite que los usuarios de teclado enfoquen el contenedor y lo desplacen con las flechas |
role="region" | Lo convierte en una región identificable para lectores de pantalla |
aria-labelledby | Le da nombre a esa región, reutilizando el texto del caption |
💡 Consejo: Esta es exactamente la técnica que usa la clase
.table-responsivede Bootstrap, que verás en el módulo 16.
Estrategia 2: apilar las celdas en móvil (tarjetas)
En pantallas pequeñas, cada fila se convierte en una “tarjeta” y cada celda muestra su encabezado mediante un atributo data-*:
<table class="tabla-apilable">
<caption>Próximos conciertos</caption>
<thead>
<tr>
<th scope="col">Artista</th>
<th scope="col">Fecha</th>
<th scope="col">Ciudad</th>
<th scope="col">Precio</th>
</tr>
</thead>
<tbody>
<tr>
<td data-label="Artista">Rosalía</td>
<td data-label="Fecha"><time datetime="2026-11-20">20/11/2026</time></td>
<td data-label="Ciudad">Barcelona</td>
<td data-label="Precio">65 €</td>
</tr>
<tr>
<td data-label="Artista">Vetusta Morla</td>
<td data-label="Fecha"><time datetime="2026-12-05">05/12/2026</time></td>
<td data-label="Ciudad">Madrid</td>
<td data-label="Precio">42 €</td>
</tr>
</tbody>
</table>
/* Adelanto de CSS: solo en pantallas de hasta 600 px */
@media (max-width: 600px) {
.tabla-apilable thead {
/* Oculta visualmente la cabecera, pero la mantiene para lectores de pantalla */
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
}
.tabla-apilable tr {
display: block;
margin-bottom: 16px;
border: 1px solid #cbd5e1;
}
.tabla-apilable td {
display: block;
border: none;
}
/* Muestra el encabezado delante de cada dato */
.tabla-apilable td::before {
content: attr(data-label) ": ";
font-weight: bold;
}
}
⚠️ Importante: Cambiar el
displayde los elementos de una tabla puede hacer que algunos navegadores pierdan la semántica de tabla para los lectores de pantalla. Esta técnica es útil, pero pruébala bien. Si buscas la opción más segura, usa la estrategia 1.
Estrategia 3: simplificar
A veces la mejor solución es mostrar menos columnas en móvil (solo las esenciales) con un enlace a la versión completa, o replantear si esos datos deben presentarse como tabla.
| Estrategia | Ventajas | Inconvenientes |
|---|---|---|
| Desplazamiento horizontal | Sencilla, conserva la semántica, funciona con cualquier tabla | Hay que desplazarse; no se ven todas las columnas a la vez |
| Apilar como tarjetas | Muy legible en móvil | Más CSS; posibles problemas de accesibilidad; no sirve para comparar filas |
| Simplificar | Lo más usable | Requiere decidir qué datos sobran |
11. Ejemplo completo: clasificación de una liga
💻 Ejemplo:
clasificacion.html:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Clasificación de la Liga Local de Baloncesto 2026-2027</title>
<style>
body { font-family: system-ui, sans-serif; max-width: 900px; margin: 0 auto; padding: 16px; }
.tabla-responsive { overflow-x: auto; }
table { border-collapse: collapse; width: 100%; min-width: 600px; }
caption { text-align: left; font-weight: bold; font-size: 1.2rem; padding-bottom: 8px; }
th, td { border: 1px solid #cbd5e1; padding: 8px 12px; }
thead th { background-color: #1e3a8a; color: white; text-align: left; }
tbody tr:nth-child(even) { background-color: #f1f5f9; }
td.numero { text-align: right; font-variant-numeric: tabular-nums; }
tfoot td { font-size: 0.85rem; color: #475569; }
</style>
</head>
<body>
<h1>Liga Local de Baloncesto</h1>
<div class="tabla-responsive" role="region" aria-labelledby="titulo-clasificacion" tabindex="0">
<table>
<caption id="titulo-clasificacion">Clasificación tras la jornada 8</caption>
<colgroup>
<col>
<col>
<col span="3">
<col>
</colgroup>
<thead>
<tr>
<th scope="col">Pos.</th>
<th scope="col">Equipo</th>
<th scope="col"><abbr title="Partidos ganados">PG</abbr></th>
<th scope="col"><abbr title="Partidos perdidos">PP</abbr></th>
<th scope="col"><abbr title="Diferencia de puntos">Dif.</abbr></th>
<th scope="col">Racha</th>
</tr>
</thead>
<tbody>
<tr>
<td class="numero">1</td>
<th scope="row">Canasteros de Vallecas</th>
<td class="numero">7</td>
<td class="numero">1</td>
<td class="numero">+84</td>
<td>4 victorias</td>
</tr>
<tr>
<td class="numero">2</td>
<th scope="row">Triples Getafe</th>
<td class="numero">6</td>
<td class="numero">2</td>
<td class="numero">+51</td>
<td>2 victorias</td>
</tr>
<tr>
<td class="numero">3</td>
<th scope="row">Rebote Alcorcón</th>
<td class="numero">4</td>
<td class="numero">4</td>
<td class="numero">−3</td>
<td>1 derrota</td>
</tr>
<tr>
<td class="numero">4</td>
<th scope="row">Mates Leganés</th>
<td class="numero">1</td>
<td class="numero">7</td>
<td class="numero">−132</td>
<td>5 derrotas</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="6">Actualizada el <time datetime="2026-11-30">30 de noviembre de 2026</time>. Los dos primeros juegan la fase final.</td>
</tr>
</tfoot>
</table>
</div>
</body>
</html>
Fíjate en los detalles:
- El nombre del equipo es un
th scope="row": es el encabezado de su fila, aunque no esté en la primera columna. - Las abreviaturas de las columnas usan
abbrcon su forma completa. - Los números negativos usan el signo menos tipográfico (
−), más legible que el guion. - La tabla tiene un
min-widthy un contenedor con desplazamiento horizontal para móviles.
12. Errores comunes y su solución
| Error | Consecuencia | Solución |
|---|---|---|
| Usar tablas para maquetar | Inaccesible, no responsive, difícil de mantener | Elementos semánticos + Flexbox/Grid |
Encabezados con td + strong | Los lectores de pantalla no los asocian a los datos | Usa th con scope |
| Filas con distinto número de celdas | Tabla descuadrada | Cuenta las celdas, teniendo en cuenta colspan y rowspan |
No restar celdas tras un rowspan | Aparecen celdas sobrantes a la derecha | Salta la posición ocupada en las filas siguientes |
Atributos obsoletos (border, cellpadding, bgcolor, align, width) | HTML inválido | Todo el aspecto con CSS |
Sin caption | El usuario no sabe qué contiene la tabla hasta leerla | Añade un caption descriptivo |
colgroup después de thead | HTML inválido | Orden: caption, colgroup, thead, tbody, tfoot |
Selector CSS table > tr que no funciona | El navegador inserta un tbody implícito | Escribe tbody explícitamente y usa tbody tr |
| Tablas enormes en móvil que rompen el diseño | Desplazamiento horizontal de toda la página | Contenedor con overflow-x: auto |
| Tabla como imagen (captura de Excel) | Inaccesible, no buscable, no copiable | Tabla HTML real |
💡 Consejo: El orden correcto de los hijos de
tablees:caption→colgroup→thead→tbody(uno o varios) →tfoot. Recuérdalo como “título, columnas, cabeza, cuerpo, pie”.
13. Ejercicios
Ejercicio 1: Tabla nutricional
Crea la tabla de información nutricional de un producto alimentario con caption, una fila de encabezados (Nutriente, Por 100 g, Por ración) y al menos 7 nutrientes (valor energético, grasas, de las cuales saturadas, hidratos de carbono, de los cuales azúcares, proteínas, sal). Los nombres de los nutrientes deben ser encabezados de fila (th scope="row"). Aplica el CSS de la sección 9 y alinea los números a la derecha.
Ejercicio 2: Tu horario semanal
Reproduce el horario semanal de tus clases o de tu trabajo (de lunes a viernes, por franjas horarias). Usa rowspan en al menos dos actividades que duren más de una franja y colspan en una fila que sea igual para todos los días (por ejemplo, el descanso). Dibuja antes la cuadrícula en papel y comprueba con el validador que no hay errores.
Ejercicio 3: Factura completa
Crea una factura con thead, tbody y tfoot: al menos 5 conceptos con cantidad, precio unitario e importe; y en el pie, base imponible, IVA del 21 % y total, con los textos del pie combinados con colspan. Calcula los importes a mano y comprueba que cuadran. Usa colgroup para dar un fondo distinto a la columna de importes.
Ejercicio 4: Comparativa de tarifas accesible
Crea una tabla que compare 4 tarifas de móvil (columnas) en 6 características (filas: precio, datos, minutos, roaming, permanencia, 5G). Las tarifas serán encabezados de columna y las características encabezados de fila, todos con scope. Añade un caption y prueba la tabla con un lector de pantalla (Narrador de Windows: Ctrl + Win + Intro; o NVDA, gratuito) para escuchar cómo se anuncian los encabezados.
Ejercicio 5: Tabla con encabezados agrupados
Crea una tabla de resultados de unas elecciones en 3 provincias, con un encabezado de dos niveles: “Votos” (que agrupe “Número” y “Porcentaje”) y “Escaños”. Usa colspan, rowspan y scope="colgroup" donde corresponda.
Ejercicio 6: De maquetación con tablas a HTML moderno
Toma el código de “maquetación con tablas” de la sección 8, amplíalo con una cabecera, un menú de 4 enlaces, un contenido con dos párrafos y un pie, y después reescríbelo sin ninguna tabla, usando div (o, si te adelantas, header, nav, main y footer) y sin atributos obsoletos. Valida ambas versiones y compara los errores.
Ejercicio 7: Tabla responsive
Crea una tabla de 8 columnas con datos de 10 países (capital, población, superficie, idioma, moneda, continente, PIB, prefijo telefónico). Hazla responsive con la estrategia 1 (contenedor accesible con desplazamiento). Después, crea una segunda versión con la estrategia 2 (tarjetas apiladas con data-label). Pruébalas en el modo dispositivo de DevTools y escribe qué ventajas e inconvenientes ves en cada una.
Ejercicio 8: Encuentra los errores
Corrige esta tabla (tiene al menos 8 problemas de validez, accesibilidad o buenas prácticas):
<table border="1" cellpadding="5" width="100%">
<thead>
<tr>
<td><b>Nombre</b></td>
<td><b>Edad</b></td>
<td><b>Ciudad</b></td>
</tr>
</thead>
<caption>Socios del club</caption>
<tr>
<td>Laura</td>
<td rowspan="2">34</td>
<td>Sevilla</td>
</tr>
<tr>
<td>Marcos</td>
<td>34</td>
<td>Huelva</td>
</tr>
</table>
Resumen
| Concepto | Sintaxis / idea clave |
|---|---|
| Tabla | <table> solo para datos tabulares |
| Fila | <tr> |
| Celda de datos | <td> |
| Celda de encabezado | <th scope="col"> o <th scope="row"> |
| Título | <caption>, primer hijo de table |
| Grupos de filas | <thead>, <tbody> (uno o varios), <tfoot> |
| Combinar columnas | colspan="n" |
| Combinar filas | rowspan="n" (en las filas siguientes, una celda menos) |
| Grupos de columnas | <colgroup><col span="2" class="..."></colgroup> |
Propiedades CSS válidas en col | background, border, width, visibility |
| Tablas complejas | id en los th + headers="id1 id2" en los td |
| Orden de los hijos | caption, colgroup, thead, tbody, tfoot |
| Maquetación | Nunca con tablas: semántica + CSS |
| CSS base | border-collapse: collapse, padding, :nth-child(even) |
| Números | text-align: right + font-variant-numeric: tabular-nums |
| Responsive | Contenedor overflow-x: auto con tabindex="0", role="region" y aria-labelledby |
| Obsoletos | border, cellpadding, cellspacing, bgcolor, align, valign, width, summary |