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, td y th
  • Dar título a una tabla con caption
  • Organizar las filas en thead, tbody y tfoot
  • Combinar celdas con colspan y rowspan
  • Agrupar columnas con colgroup y col
  • Hacer tablas accesibles con scope y headers
  • 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 (td o th) 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 td vacío o un th vacío; algunos autores prefieren td para 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 de table. 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 usar figcaption en lugar de caption, pero no los dos con el mismo texto. Y no uses un h2 suelto encima de la tabla como sustituto del caption: 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 thead y tfoot en cada página.
  • Desplazamiento: con CSS puedes dejar fija la cabecera mientras se desplaza el cuerpo.

💡 Consejo: Puedes usar varios tbody en una misma tabla para agrupar filas relacionadas (por ejemplo, un tbody por mes o por categoría de productos).

📘 Concepto: Si escribes filas directamente dentro de table sin tbody, el navegador crea un tbody automá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 como table > tr, el selector no funciona: en el DOM, las filas son hijas de tbody.


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 con colspan, 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

  1. Dibuja la tabla en papel (o en una hoja de cálculo) con todas sus celdas.
  2. Marca las celdas combinadas.
  3. Escribe el HTML fila a fila. En cada fila, salta las posiciones que ya están ocupadas por un rowspan de una fila superior.
  4. 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; }
  • colgroup va justo después del caption y antes de thead.
  • Cada col representa una columna; span="2" hace que un col represente dos.

⚠️ Importante: A través de col solo funcionan unas pocas propiedades CSS: background, border (con border-collapse: collapse), width y visibility: collapse. No puedes cambiar el color del texto, la alineación ni el padding de una columna con col. Para eso, usa selectores como td: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 headers son 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 summary de table, que verás en tutoriales antiguos, está obsoleto en HTML5. Si la tabla necesita explicación, escríbela en el caption o 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-responsive de 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 display de 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 abbr con su forma completa.
  • Los números negativos usan el signo menos tipográfico (−), más legible que el guion.
  • La tabla tiene un min-width y 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 table es: 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