Etiqueta tbody en HTML: grupos de filas de datos
La etiqueta <tbody> agrupa las filas de datos de una tabla HTML. Sirve para separar esos datos de los encabezados y los totales, o para organizar categorías dentro de una misma tabla. Úsala, por ejemplo, para reunir los cursos de un centro de formación y aplicar estilos a sus filas sin afectar a la cabecera.
Ejemplo mínimo: un curso en el cuerpo de datos
<table>
<caption>Duración de los cursos, en horas</caption>
<thead>
<tr><th scope="col">Curso</th><th scope="col">Horas</th></tr>
</thead>
<tbody>
<tr><th scope="row">HTML</th><td>12</td></tr>
</tbody>
</table>
Pega el fragmento en el cuerpo de un documento HTML. Verás el título, los encabezados «Curso» y «Horas», y una fila que relaciona HTML con 12 horas. El navegador no dibuja necesariamente bordes sin CSS.
La fila de HTML pertenece al cuerpo de datos. Puede contener un encabezado de fila válido: scope="row" va en su <th>, mientras que <td> contiene la duración. <tbody> no transforma todas sus celdas en datos ni añade texto visible propio. Cambiar 12 por 14 modifica el dato, no la agrupación.
Sintaxis y contexto válido
Escribe <tbody>…</tbody> dentro de <table>, después de <caption>, <colgroup> y <thead> si existen, y antes del <tfoot> opcional. Puede haber varios cuerpos consecutivos. No mezcles cuerpos explícitos con filas <tr> directamente bajo la tabla en la estructura que pretendes crear.
El modelo de contenido de <tbody> en el estándar HTML admite cero o más filas <tr> y elementos de apoyo a scripts, como <script> o <template>. Las celdas <th> y <td> van dentro de las filas, nunca directamente dentro de <tbody>. Tampoco es un contenedor para párrafos o listas sueltos.
Los ejemplos conservan ambas etiquetas para mostrar los límites. Según las reglas de omisión descritas por MDN, la apertura puede omitirse si el primer contenido es un <tr> y no sigue inmediatamente a otro grupo cuyo cierre se omitió. El cierre puede omitirse antes de otro <tbody>, de <tfoot> o al terminar el contenido de la tabla. Un cuerpo vacío necesita su apertura explícita.
¿Por qué aparece tbody si no lo escribí?
Al analizar text/html, el navegador puede insertar un <tbody> que agrupe las filas escritas directamente bajo <table>. El código fuente es el texto guardado; el DOM es el árbol de elementos que utiliza el navegador. No tienen por qué mostrar exactamente las mismas etiquetas.
Prueba a retirar solo <tbody> y </tbody> del ejemplo mínimo, conservando la fila. En «Ver código fuente» ya no estarán; en el inspector de elementos seguirás encontrando un cuerpo alrededor de esa fila. Es el comportamiento del analizador HTML para tablas, no una carga automática de datos ni una corrección de cualquier error de autoría.
Esto afecta a CSS: table > tr busca filas que sean hijas directas de la tabla y no alcanza las que están dentro del cuerpo implícito. table > tbody > tr sí alcanza esas filas. table tr también alcanza filas de cabecera, pie e incluso tablas anidadas; elige el selector según el alcance que necesites.
Caso práctico: dos grupos de cursos
Guarda este documento como cursos-tbody.html, con codificación UTF-8, y ábrelo en tu navegador. Los datos son ficticios y mantienen las duraciones de HTML básico, CSS y JavaScript usadas en la guía de <table>; el curso DOM amplía la oferta. No necesita 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>Cursos por categoría</title>
<style>
body {
max-width: 44rem;
margin: 2rem auto;
padding: 0 1rem;
font-family: sans-serif;
line-height: 1.5;
color: #222;
background: #fff;
}
.desplazamiento { max-width: 100%; overflow-x: auto; }
.desplazamiento:focus-visible { outline: 3px solid #2456a6; }
.cursos { width: 100%; min-width: 32rem; border-collapse: collapse; }
.cursos caption { padding-block: 0.75rem; text-align: left; }
.cursos th, .cursos td {
padding: 0.5rem;
border: 1px solid #666;
text-align: left;
}
/* No encuentra filas hijas directas de esta tabla. */
.cursos > tr { background: #ffd6d6; }
/* Sí encuentra las cuatro filas de datos. */
.cursos > tbody > tr { background: #eef5ff; }
.cursos tbody + tbody > tr:first-child > * {
border-top: 3px solid #222;
}
</style>
</head>
<body>
<h1>Cursos por categoría</h1>
<p>Cada categoría reúne dos cursos. La duración se expresa en horas.</p>
<p>En pantallas pequeñas, desplaza la tabla para leer todas las columnas.</p>
<div class="desplazamiento" role="region"
aria-label="Tabla de cursos por categoría" tabindex="0">
<table class="cursos">
<caption>Oferta de cursos y duración, en horas</caption>
<thead>
<tr>
<th scope="col">Categoría</th>
<th scope="col">Curso</th>
<th scope="col">Horas</th>
</tr>
</thead>
<tbody>
<tr>
<th rowspan="2" scope="rowgroup">Estructura y estilo</th>
<th scope="row">HTML básico</th><td>12</td>
</tr>
<tr><th scope="row">CSS</th><td>16</td></tr>
</tbody>
<tbody>
<tr>
<th rowspan="2" scope="rowgroup">Programación</th>
<th scope="row">JavaScript</th><td>20</td>
</tr>
<tr><th scope="row">DOM</th><td>8</td></tr>
</tbody>
</table>
</div>
</body>
</html>
Verás tres columnas y cuatro filas de datos con fondo azul claro. «Estructura y estilo» identifica HTML básico y CSS; «Programación» identifica JavaScript y DOM. La línea gruesa separa los grupos. Los nombres escritos comunican la categoría sin depender del color. En móvil, la tabla se desplaza dentro de su región; con teclado, llega mediante Tab y usa las flechas horizontales.
Encabezados de fila y de grupo
scope="row" identifica el curso de una fila. scope="rowgroup" se justifica aquí porque la categoría encabeza todas las filas de su propio <tbody>. Ambos atributos pertenecen a <th>. rowspan="2" hace que la celda de categoría ocupe dos filas; no crea por sí solo el grupo. La guía de encabezados irregulares de W3C WAI explica esta asociación.
El cuerpo aporta agrupación, pero no sustituye los encabezados ni garantiza un anuncio idéntico en todos los lectores de pantalla. Si la tabla tiene asociaciones más complejas, puede necesitar id y headers en sus celdas.
Comprobaciones y cambios para entender el DOM
- En el inspector, comprueba los dos
<tbody>, con dos<tr>cada uno..cursos > trno encuentra ninguno;.cursos > tbody > trencuentra cuatro. La regla roja no se aplica. - Cambia el fondo azul: cambia la presentación de los datos y la cabecera conserva su aspecto. Retira el CSS: los cursos, las categorías y sus asociaciones siguen en el HTML.
- Retira las cuatro etiquetas que delimitan ambos cuerpos y vuelve a abrir el archivo. El analizador reúne las cuatro filas en un único cuerpo implícito. El azul sigue aplicándose, pero desaparece la separación entre cuerpos. Ahora los encabezados con
scope="rowgroup"apuntan al mismo grupo, por lo que ya no expresan las categorías previstas: restaura los dos cuerpos.
Atributos, errores y etiquetas relacionadas
<tbody> no tiene atributos específicos vigentes; admite globales como id, class o lang. No existe un atributo suyo para cargar datos automáticamente. background y text-align son propiedades CSS; rows es una propiedad DOM de JavaScript, no un atributo HTML. Los antiguos align, bgcolor y valign deben sustituirse por CSS, como explica la referencia de atributos de MDN.
¿Puedo usar varios tbody? Sí: crea uno por cada conjunto de filas que tenga sentido agrupar. No conviertas cada fila en un cuerpo por costumbre. Para una lista sin categorías suele bastar uno.
¿Por qué no funciona mi selector? Inspecciona el DOM, comprueba si hay un cuerpo intermedio y revisa el selector y las reglas que lo sobrescriben. La ausencia de <tbody> en el archivo no demuestra que esté ausente del DOM.
No escribas celdas directas ni sustituyas el cuerpo por un <div> dentro de la tabla. El navegador puede reparar marcado incorrecto, pero esa reparación no justifica usarlo.
Elige cada elemento por su función: <table> contiene la tabla completa; <tr> crea una fila; <thead> reúne los encabezados y <tfoot> los resúmenes. <th> identifica una fila, columna o grupo. <tbody> reúne las filas de datos, y <caption> da título al conjunto.
Compatibilidad y fuentes
Revisión del 4 de octubre de 2026: <tbody> tiene soporte consolidado en los navegadores actuales, según la compatibilidad del elemento en MDN. Los dos ejemplos funcionan sin JavaScript; el inspector sirve para comprobar su estructura, no para generarla. Las asociaciones accesibles y su lectura dependen también del navegador y de la tecnología de asistencia.
El HTML Living Standard sobre <tbody> fija su significado y sus restricciones; las fuentes de análisis HTML, MDN y W3C WAI enlazadas en cada sección permiten ampliar las comprobaciones.