Etiqueta table en HTML: tablas de datos accesibles
La etiqueta <table> representa datos con relaciones entre filas y columnas. Sirve para comparar información que comparte las mismas categorías: por ejemplo, los nombres de unos cursos, su duración y las plazas disponibles. Úsala cuando cada valor necesite una fila y una columna para entenderse; para distribuir las secciones de una web, utiliza CSS.
Ejemplo mínimo: un curso y su duración
<table>
<caption>Cursos disponibles</caption>
<thead>
<tr><th scope="col">Curso</th><th scope="col">Horas</th></tr>
</thead>
<tbody>
<tr><th scope="row">HTML básico</th><td>12</td></tr>
</tbody>
</table>
Pega el fragmento en el cuerpo de un documento HTML. Verás un título, dos encabezados de columna y una fila que relaciona «HTML básico» con 12 horas. En un documento sin estilos, el navegador no dibuja necesariamente bordes; este sitio añade presentación a sus resultados.
| Curso | Horas |
|---|---|
| HTML básico | 12 |
Si cambias 12 por 14, cambia la duración de ese curso. Si retiras <table> y conservas su contenido, las filas y celdas quedan fuera de su contexto válido: el analizador puede descartar sus etiquetas y perderse la cuadrícula. Sustituirla por un <div> tampoco crea una tabla semántica.
Sintaxis y contexto válido
Escribe <table>…</table> con apertura y cierre: ninguno se puede omitir. Es contenido de flujo y puede aparecer, por ejemplo, dentro de <main>, <section> o <div>. Un <p> admite contenido de frase, por lo que no es un contenedor válido para una tabla.
El modelo de contenido de table en el estándar HTML fija este orden:
- Un
<caption>opcional, como primer hijo que sea un elemento. - Cero o más
<colgroup>, si necesitas declarar grupos de columnas. - Un
<thead>opcional, para las filas de encabezados. - Cero o más
<tbody>, o una o más filas<tr>directas; no mezcles ambas alternativas en el mismo nivel. - Un
<tfoot>opcional, al final, para filas de resumen.
También se permiten elementos de soporte de scripts, como <script> y <template>, intercalados; estos ejemplos no los necesitan. Los datos van en celdas dentro de filas, no como texto suelto o párrafos directamente bajo <table>.
El tbody que añade el navegador
Al analizar HTML escrito con <tr> directamente bajo <table>, el navegador inserta un <tbody> en el DOM. Por eso un selector como table > tr puede no encontrar filas aunque el código fuente parezca tenerlas allí. Escribir <tbody> explícitamente, como en los ejemplos, facilita leer la estructura y usar table > tbody > tr. La reparación del analizador no convierte cualquier tabla mal escrita en HTML válido.
Las partes de una tabla de cursos
<table> reúne el conjunto. Cada parte tiene una función:
<caption>identifica los datos de toda la tabla.<thead>,<tbody>y<tfoot>agrupan filas de encabezados, datos y resumen, respectivamente.<tr>representa una fila.<th>representa un encabezado; puede identificar una columna o una fila.<td>contiene un dato asociado con esos encabezados.
En nuestros cursos, «Horas» es un encabezado de columna, «HTML básico» identifica una fila y 12 es un dato. Estar en <thead> no convierte automáticamente un <td> en encabezado. Tampoco <tfoot> calcula nada: los totales del siguiente ejemplo están escritos a mano.
Caso práctico: tres cursos y sus totales
Guarda este documento completo como cursos-table.html, con codificación UTF-8, y ábrelo en tu navegador. Los datos son ficticios; incluye todo el CSS y funciona sin servidor, recursos externos ni JavaScript.
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Oferta de cursos</title>
<style>
body {
max-width: 48rem;
margin: 2rem auto;
padding: 0 1rem;
font-family: sans-serif;
line-height: 1.5;
color: #222;
background: #fff;
}
.cursos-scroll {
max-width: 100%;
overflow-x: auto;
}
.cursos-scroll:focus-visible {
outline: 3px solid #1554ad;
outline-offset: 3px;
}
.cursos {
width: 100%;
min-width: 32rem;
border-collapse: collapse;
}
.cursos caption {
padding: 0.75rem;
text-align: left;
font-weight: bold;
}
.cursos th,
.cursos td {
border: 1px solid #666;
padding: 0.75rem;
text-align: left;
}
.cursos thead,
.cursos tfoot {
background: #eaf2ff;
}
.cursos td {
text-align: right;
}
</style>
</head>
<body>
<h1>Oferta de cursos</h1>
<p>Si la tabla no cabe, enfoca su región con Tab y usa
las flechas izquierda y derecha para desplazarte.</p>
<div class="cursos-scroll" tabindex="0" role="region"
aria-label="Oferta de cursos con desplazamiento horizontal">
<table class="cursos">
<caption>Cursos: duración en horas y plazas disponibles</caption>
<thead>
<tr>
<th scope="col">Curso</th>
<th scope="col">Horas</th>
<th scope="col">Plazas</th>
</tr>
</thead>
<tbody>
<tr><th scope="row">HTML básico</th><td>12</td><td>20</td></tr>
<tr><th scope="row">CSS</th><td>16</td><td>15</td></tr>
<tr><th scope="row">JavaScript</th><td>20</td><td>12</td></tr>
</tbody>
<tfoot>
<tr><th scope="row">Total de la oferta</th><td>48</td><td>47</td></tr>
</tfoot>
</table>
</div>
<p>Las 48 horas suman la duración de los tres cursos;
las 47 plazas suman sus cupos, no personas únicas.</p>
</body>
</html>
Primero identifica las tres columnas; después comprueba cada curso y la última fila: 12 + 16 + 20 = 48 horas y 20 + 15 + 12 = 47 plazas. Los bordes provienen de CSS. border-collapse: collapse une los bordes adyacentes en lugar de mostrar una separación entre ellos.
Si la tabla no cabe, enfoca su región con Tab y desplázate con las flechas.
| Curso | Horas | Plazas |
|---|---|---|
| HTML básico | 12 | 20 |
| CSS | 16 | 15 |
| JavaScript | 20 | 12 |
| Total de la oferta | 48 | 47 |
48 horas entre los tres cursos y 47 plazas disponibles.
Cambia las plazas de CSS de 15 a 18 y actualiza el total a 50: HTML no sincroniza las dos cifras. Al retirar el CSS se conservan título, encabezados y datos, aunque desaparecen los bordes y la anchura mínima. Al retirar <tfoot> junto con su contenido desaparece el resumen, no las filas de cursos.
Encabezados y lectura accesible
En estas tablas, scope="col" identifica encabezados de columna y scope="row" identifica el curso o total que encabeza una fila. Así, el 15 se interpreta con «CSS» y «Plazas». El tutorial de W3C WAI para tablas con dos direcciones de encabezados explica este patrón. El título y las unidades aportan contexto que los números solos no ofrecen.
La tabla ya tiene semántica nativa: no necesita role="table" ni convertir sus celdas en un widget ARIA. Los lectores de pantalla pueden navegar por celdas y aprovechar sus encabezados; el anuncio concreto depende de la combinación de navegador, tecnología de asistencia y configuración.
Cuando hacen falta headers e id
Si añades plazas de mañana y tarde, una cifra puede tener varios encabezados. Considera simplificar o separar la tabla. Si necesitas conservar relaciones complejas, asigna un id único en el documento a cada <th> pertinente y enuméralos, separados por espacios, en headers de la celda correspondiente. Cada referencia debe apuntar a un <th> de esa misma tabla. W3C WAI muestra esta técnica en tablas con encabezados de varios niveles.
Este fragmento, para pegar en el cuerpo de un documento HTML, desglosa las 20 plazas de HTML básico: 12 por la mañana y 8 por la tarde. Las horas siguen siendo 12; no se suman los turnos a la duración.
<table>
<caption>HTML básico: plazas por turno</caption>
<thead>
<tr>
<th id="table-curso" rowspan="2">Curso</th>
<th id="table-horas" rowspan="2">Horas</th>
<th id="table-plazas" colspan="2">Plazas</th>
</tr>
<tr>
<th id="table-manana" headers="table-plazas">Mañana</th>
<th id="table-tarde" headers="table-plazas">Tarde</th>
</tr>
</thead>
<tbody>
<tr>
<th id="table-html" headers="table-curso">HTML básico</th>
<td headers="table-html table-horas">12</td>
<td headers="table-html table-plazas table-manana">12</td>
<td headers="table-html table-plazas table-tarde">8</td>
</tr>
</tbody>
</table>
| Curso | Horas | Plazas | |
|---|---|---|---|
| Mañana | Tarde | ||
| HTML básico | 12 | 12 | 8 |
La celda con 8 se asocia explícitamente con «HTML básico», «Plazas» y «Tarde». rowspan y colspan cambian la cuadrícula; por sí solos no expresan toda esa asociación. No copies los mismos IDs dos veces en una página. Esta ampliación ilustra una decisión distinta; la tabla simple anterior puede conservar scope.
Tablas en móvil y revisión por teclado
En el documento completo, overflow-x: auto pertenece al contenedor, y min-width pertenece a la tabla. Si no cabe, se desplaza dentro de esa región sin ensanchar toda la página. tabindex="0" permite llegar con Tab; role="region" y su nombre identifican la zona desplazable. El contenedor facilita el acceso, pero la semántica de los datos sigue perteneciendo a <table>.
Prueba el archivo a 375 y 1280 px de ancho. En móvil, llega a la región con Tab, comprueba el contorno de foco y usa las flechas para alcanzar «Plazas». Con Shift+Tab y Tab debes poder salir. En escritorio, confirma que el título y las tres columnas caben. Repite con JavaScript desactivado: estos ejemplos mantienen los mismos datos y desplazamiento porque no contienen scripts.
No ocultes columnas esenciales por defecto ni cambies celdas y filas a bloques sin revisar sus relaciones. Comprueba también las asociaciones con una tecnología de asistencia. Nuestra revisión local en Chromium verifica nombres, roles, cuadrícula y teclado; no sustituye la lectura humana con un lector de pantalla. La referencia de overflow de MDN explica el acceso por teclado a contenedores desplazables.
Atributos y propiedades: dónde va cada cosa
<table> admite atributos globales, como id, class y lang, pero no tiene atributos específicos vigentes. class permite seleccionarla desde CSS; no añade bordes por sí sola.
scope pertenece a <th>. colspan, rowspan y headers pertenecen a las celdas <th> o <td>, no a <table>. border, padding, width y border-collapse en los ejemplos son propiedades CSS. En JavaScript, tabla.rows sería una propiedad DOM; estos ejemplos no requieren usarla.
Los atributos históricos border, cellpadding, cellspacing, bgcolor, align y width deben sustituirse por CSS. Usa border y padding en celdas, background-color para fondos y width para anchura. Para separación entre celdas utiliza border-spacing con bordes separados; no produce esa separación cuando usas border-collapse: collapse. MDN documenta estos atributos obsoletos de table.
Errores y preguntas frecuentes
¿Table sirve para maquetar una web? No: representa datos tabulares. Para una cabecera, una barra lateral o un conjunto de tarjetas, usa contenedores adecuados y CSS Grid o Flexbox. Si solo necesitas una lista de cursos sin categorías comparables, una lista <ul> puede resultar más clara.
¿Cómo hago una tabla accesible? Identifica su tema con <caption>, escribe encabezados reales <th> y relaciona los datos con scope o, cuando sea necesario, headers e IDs. Explica unidades y totales. Después revisa lectura, teclado y anchuras pequeñas; añadir un título no corrige encabezados equivocados.
¿Por qué no aparecen bordes? La tabla puede ser correcta y carecer de bordes visibles. Revisa la clase, el selector y la propiedad CSS border sobre las celdas. No añadas el atributo histórico border para corregir una hoja de estilos.
Evita celdas vacías como separadores, <td> en negrita simulando encabezados y filas con posiciones faltantes o solapadas. Si un dato no está disponible, escribe una indicación comprensible, como «No disponible». Reserva las celdas combinadas para relaciones reales y mantén completa la cuadrícula.
Compatibilidad y etiquetas relacionadas
Consulta realizada el 4 de octubre de 2026: el estándar y la referencia de table en MDN registran soporte en los motores actuales. Los ejemplos usan HTML y CSS ordinarios, sin depender de una API JavaScript. La revisión a 375/1280 px se realizó en Chromium 153; no constituye una prueba de todos los motores ni garantiza anuncios idénticos de lectores de pantalla.
Para ampliar el título, consulta la guía de caption. Las guías de colgroup y col explican cómo declarar columnas. <tr>, <th>, <td>, <thead>, <tbody> y <tfoot> cumplen las funciones descritas aquí; sus guías se enlazarán cuando estén integradas.