Тег tbody (от английского table body, «тело таблицы») собирает строки с данными: всё, что не относится к шапке thead и подвалу tfoot. Секций tbody в одной таблице может быть несколько, и каждая образует отдельную группу строк. Записывать тег необязательно, но браузер почти всегда создаёт его сам, и это влияет на стили и скрипты.
Зачем таблицу делят на секции и в каком порядке они выводятся, объясняет урок «Как разделить таблицу: thead, tbody и tfoot». Здесь разберём то, что относится к самому tbody: какие секции достраивает браузер, как красить группы строк и их границы, как закреплять заголовок группы, как прокручивать и скрывать тело таблицы, как его печатать и как добавлять строки из скрипта.
<table>
<thead>
<tr><th>Чай</th><th>Цена</th></tr>
</thead>
<tbody>
<tr><td>Сенча</td><td>100</td></tr>
</tbody>
<tbody>
<tr><td>Пуэр</td><td>140</td></tr>
</tbody>
</table>
В этой статье
- Тег tbody в HTML: что это и что записывать внутри
- Пропуск тегов tbody и парсер: что достраивает браузер
- Стили tbody: чередование групп и строк, границы и высота
- Группы в tbody: заголовок группы, ссылка и закрепление
- Прокрутка и скрытие tbody: что работает и что нет
- Печать tbody: как не разорвать группу строк
- Тег tbody и JavaScript: tBodies, insertRow и createTBody
- Частые ошибки с тегом tbody
Тег tbody в HTML: что это и что записывать внутри
Внутри tbody записывают только строки tr, а в них ячейки td и, если нужен заголовок строки, th. Своих атрибутов у тега нет, только глобальные. По умолчанию браузер выводит его отдельной секцией (display: table-row-group), а для скринридера это группа строк с ролью rowgroup.
| Запись | Что получается | Пример записи |
|---|---|---|
tr в tbody | Строка данных | <tr><td>Сенча</td><td>100</td></tr> |
th в начале строки | Заголовок строки, а не ячейка данных | <th scope="row">Сенча</th> |
Несколько tbody подряд | Каждая секция выводится отдельной группой строк, колонки у всех общие | <tbody>...</tbody><tbody>...</tbody> |
rowspan через границу секции | Chrome обрезает ячейку по границе своей секции | <td rowspan="4">Зелёные</td> |
Атрибуты align, valign, bgcolor | Устарели | Свойства text-align, vertical-align и background |
Объединение ячеек не выходит за секцию: если ячейка с rowspan="4" стоит в группе из двух строк, Chrome растянет её только на эти две, строки следующего tbody она не захватит.
<tbody>
<tr><td rowspan="4">Зелёные</td><td>Сенча</td></tr>
<tr><td>Улун</td></tr>
</tbody>
<tbody>
<tr><td>Чёрные</td><td>Ассам</td></tr>
</tbody>
Пропуск тегов tbody и парсер: что достраивает браузер
По стандарту tbody стоит в table после caption, colgroup и thead, но только если у таблицы нет строк tr, записанных прямо в ней. Начальный тег можно опустить, когда первым внутри идёт tr и перед ним нет секции с опущенным закрывающим тегом. Закрывающий тег можно опустить, когда следом идёт другая секция tbody или tfoot либо таблица закончилась. Пустой tbody без начального тега не записать. Браузер достраивает пропущенное, поэтому в DOM секция tbody есть почти всегда, даже если в коде её нет.
| Как записано | Что делает браузер | Пример записи |
|---|---|---|
Строки tr прямо в table | Создаёт tbody и переносит строки в него: селектор table > tr ничего не находит | <table><tr><td>Сенча</td></tr></table> |
Строка tr после закрытого tbody | Создаёт для неё новый tbody | <tbody>...</tbody><tr>...</tr> |
Строка tr после tfoot | Создаёт новый tbody: в DOM он стоит после tfoot, а на экране строка выводится выше подвала | <tfoot>...</tfoot><tr>...</tr> |
Опущен </thead>, а <tbody> не записан | Строки с данными остаются внутри thead | <thead><tr><th>Чай<tr><td>Сенча |
Опущены </tbody> между секциями | Достраивает закрывающие теги, секции разделены верно | <tbody><tr><td>1<tbody><tr><td>2 |
Текст или div внутри tbody | Выносит их из таблицы, они оказываются перед ней | <tbody>Текст<tr>...</tr></tbody> |
Пример: как браузер читает запись
Выберите запись: вверху виден код, внизу — что получилось на экране.
Из таблицы следует простое правило: если закрывающий тег шапки опущен, секцию tbody стоит записывать явно. Как браузер разбирает запись самого thead, описано на странице thead.
Допустимые места записи перечислены в описании элемента в стандарте HTML.
Стили tbody: чередование групп и строк, границы и высота
Наследуемые свойства color, font-size и text-align, фон секции и граница при border-collapse: collapse работают у tbody так же, как у thead: подробности есть на странице thead. У тела таблицы свои особенности: несколько секций, нумерация строк и высота.
| Запись | Что получается | Пример записи |
|---|---|---|
tbody:nth-child(even) | Выбирает первую группу: thead считается первым дочерним элементом таблицы, если перед ним нет caption и colgroup: они тоже участвуют в счёте | tbody:nth-child(even) { background: #ffd9a8; } |
tbody:nth-of-type(even) | Выбирает вторую группу: в счёте только теги tbody | tbody:nth-of-type(even) { background: #ffd9a8; } |
tbody tr:nth-child(even) | Выбирает чётные строки, а нумерация начинается заново в каждой группе | tbody tr:nth-child(even) { background: #ffd9a8; } |
border-top у tbody + tbody | Граница между группами рисуется только при border-collapse: collapse | tbody + tbody { border-top: 3px solid #c0292e; } |
border-top у tbody + tbody td | Граница рисуется в любом режиме: она записана на ячейках | tbody + tbody td { border-top: 3px solid #c0292e; } |
height у tbody | Задаёт минимум: секция растягивается, и строки получают лишнее место поровну | tbody { height: 90px; } |
Пример: стили групп и строк
Выберите правило и при желании включите border-collapse: collapse: закрашенное и обведённое видно сразу.
tbody tr:nth-child(even) {
background: #f1ede7;
}
tbody:nth-of-type(even) {
background: #eaf3f0;
}
tbody + tbody td {
border-top: 3px solid #2c5e77;
}
Группы в tbody: заголовок группы, ссылка и закрепление
Если групп несколько, у каждой обычно есть название. Его записывают первой строкой секции: ячейка th с colspan на всю ширину таблицы и scope="rowgroup", так она относится ко всем строкам своей группы. Секции можно дать id и сослаться на группу по нему.
<tbody id="zelenye">
<tr><th scope="rowgroup" colspan="2">Зелёные</th></tr>
<tr><td>Сенча</td><td>100</td></tr>
<tr><td>Улун</td><td>120</td></tr>
</tbody>
| Запись | Что получается | Пример записи |
|---|---|---|
th с scope="rowgroup" | Заголовок группы на всю ширину таблицы, относится ко всем строкам секции | <th scope="rowgroup" colspan="2">Зелёные</th> |
Ссылка на id секции | Переход к группе: браузер прокручивает страницу или блок с таблицей до неё | <a href="#zelenye">Зелёные</a> |
tbody:target | Выбирает группу, к которой перешли по ссылке, и позволяет её подсветить | tbody:target td { background: #fff2c9; } |
tbody th с position: sticky | Заголовки закрепляются у верхнего края; предыдущий не уходит вместе со своей секцией, его перекрывает следующий | tbody th { position: sticky; top: 0; } |
| Закреплённый заголовок без фона | Строки группы видны сквозь заголовок | tbody th { background: #fde4e4; } |
Пример: заголовки групп, ссылка и закрепление
Выберите вариант. В закреплённых вариантах блок прокручен вниз, и виден заголовок второй группы.
Закрепление работает так же, как у шапки: position: sticky и top. Отличие в том, что границей для закреплённой ячейки служит вся таблица, а не её секция: заголовки всех групп остаются у верхнего края, а виден последний из них, потому что он перекрывает предыдущие. Поэтому у заголовка нужен непрозрачный фон. Если вместе с ними закреплена шапка, top у заголовков групп делают равным высоте шапки, иначе они встанут на её место.
tbody th {
position: sticky;
top: 0;
background: #fde4e4;
}
tbody:target td {
background: #fff2c9;
}
Прокрутка и скрытие tbody: что работает и что нет
Часто нужно прокручивать только строки таблицы, оставив шапку на месте. Свойство overflow на tbody не действует: секция таблицы растёт по содержимому. Приём с display: block включает прокрутку, но разрывает связь колонок шапки и тела, и без table-layout: fixed они расходятся. Надёжнее обёртка с max-height и overflow: auto и закреплённая шапка: как её закрепить, описано на странице thead.
| Запись | Результат | Пример записи |
|---|---|---|
overflow: auto у tbody | Прокрутки нет, секция растёт по содержимому | tbody { height: 90px; overflow: auto; } |
display: block у tbody | Прокрутка есть, колонки шапки и тела разъезжаются | tbody { display: block; height: 90px; overflow: auto; } |
display: block у tbody и table-layout: fixed у шапки и строк | Колонки в целом совпадают, но если у полосы прокрутки есть ширина, тело становится на неё уже, а шапка нет, и колонки слегка сдвигаются | thead, tbody tr { display: table; width: 100%; table-layout: fixed; } |
Обёртка с max-height и overflow: auto, у шапки position: sticky | Шапка закреплена, колонки общие | .wrap { max-height: 110px; overflow: auto; } |
Пример: прокрутка тела таблицы
Выберите вариант и сравните положение колонок шапки и тела. В вариантах с прокруткой тело уже прокручено вниз.
Группу строк скрывают, когда нужны фильтр или сворачиваемый раздел. Атрибут hidden и display: none убирают секцию из вывода целиком. Значение visibility: collapse тоже освобождает место, но скрытая строка продолжает влиять на ширину колонки. Значение visibility: hidden строки скрывает, а место оставляет пустым.
| Запись | Результат | Пример записи |
|---|---|---|
Атрибут hidden | Группа убрана из вывода, место и ширина колонки освобождены | <tbody hidden>...</tbody> |
display: none | То же, что атрибут hidden | tbody.old { display: none; } |
visibility: collapse | Место освобождено, но колонка остаётся шириной по скрытым строкам | tbody.old { visibility: collapse; } |
visibility: hidden | Группа невидима, а её место занято | tbody.old { visibility: hidden; } |
Пример: способы скрыть группу строк
Выберите способ: в статусе указаны высота таблицы и ширина первой колонки. В первой группе есть длинное название.
Печать tbody: как не разорвать группу строк
При печати на нескольких страницах шапка таблицы повторяется, а строки tbody просто продолжаются, и граница страницы может разрезать группу посередине. В пробной таблице из семи групп по десять строк на листах A4 одна группа оказалась разорвана: часть строк осталась на одной странице, остальные перешли на следующую. Правило break-inside: avoid у tbody переносит такую группу целиком. Оно рассчитано на группы короче страницы, а сколько групп разорвётся, зависит от высоты строк. Результат видно в предпросмотре печати: Ctrl + P.
@media print {
tbody {
break-inside: avoid;
}
}
| Запись | Результат при печати | Пример записи |
|---|---|---|
Обычный tbody | Группа может разорваться между страницами | <tbody>...</tbody> |
tbody с break-inside: avoid | Группа, которая не помещается в остаток страницы, переносится на следующую целиком | tbody { break-inside: avoid; } |
Как шапка повторяется на каждой странице и как это отключить, описано на странице thead.
Тег tbody и JavaScript: tBodies, insertRow и createTBody
Секция tbody в скрипте представлена интерфейсом HTMLTableSectionElement. У таблицы есть живая коллекция tBodies со всеми секциями, а у секции свои rows, insertRow() и deleteRow(). Строки лучше добавлять в секцию, а не в таблицу: при наличии подвала вызов у таблицы отправит строку в подвал.
| Запись | Что делает | Пример записи |
|---|---|---|
table.tBodies | Возвращает живую коллекцию всех секций tbody | table.tBodies.length |
table.createTBody() | Создаёт секцию после последнего tbody, а если секций tbody нет, то в конце таблицы | table.createTBody(); |
tbody.insertRow(-1) | Добавляет строку в конец секции | tbody.insertRow(-1); |
table.insertRow(-1) | Добавляет строку после последней строки таблицы: она попадает в ту секцию, где эта строка стоит (при наличии tfoot это он), а в таблице без строк и без tbody создаётся tbody | table.insertRow(-1); |
row.rowIndex | Возвращает номер строки во всей таблице, строки шапки входят в счёт | row.rowIndex |
row.sectionRowIndex | Возвращает номер строки внутри своей секции | row.sectionRowIndex |
tbody.replaceChildren() | Очищает секцию, сам тег tbody остаётся | tbody.replaceChildren(); |
Пример: строки и секции из скрипта
Выберите действие: вверху виден код, под ним таблица и результат, который считает сама страница.
const items = [{ name: 'Сенча', price: 100 }];
const tbody = document.querySelector('tbody');
tbody.replaceChildren();
for (const item of items) {
const row = tbody.insertRow();
row.insertCell().textContent = item.name;
row.insertCell().textContent = item.price;
}
Сортировку делают перестановкой готовых строк: append() переносит существующую строку, а не копирует её, поэтому шапка и подвал остаются нетронутыми. Ячейки, созданные методом insertCell(), получаются тегами td; для th элемент создают через document.createElement('th').
Частые ошибки с тегом tbody
Типичные ошибки с секцией tbody и как их исправить.
| Ошибка | Что происходит | Как исправить |
|---|---|---|
table > tr в CSS или JavaScript | Ничего не находит: строки находятся в tbody, который создал браузер | table > tbody > tr или table tr |
Опущен </thead>, а tbody не записан | Строки с данными остаются в thead | Записать <tbody> или </thead> |
tbody:nth-child(even) | Выбирает первую группу: thead участвует в счёте | tbody:nth-of-type(even) |
table.insertRow(-1) при наличии tfoot | Строка попадает в подвал | tbody.insertRow(-1) |
tbody { overflow: auto; } для прокрутки | Прокрутки нет | Обёртка с max-height и overflow: auto |
tbody { display: block; } для прокрутки | Колонки шапки и тела разъезжаются | Обёртка и закреплённая шапка |
visibility: collapse для скрытия группы | Колонка остаётся шириной по скрытым строкам | Атрибут hidden или display: none |
tbody { border: 1px solid; } без collapse | Рамка не рисуется | Добавить border-collapse: collapse или записать границу на ячейках |
| Закреплённый заголовок группы без фона | Строки группы видны сквозь заголовок | tbody th { background: #fff; } |
rowspan длиннее группы | Chrome обрезает ячейку по границе секции | Считать строки группы и не выходить за них |
Текст внутри tbody | Браузер выносит его перед таблицей | Записывать текст в ячейки td |
| Группа разорвана при печати | Часть строк на одной странице, часть на другой | tbody { break-inside: avoid; } |
Устаревшие align, valign, bgcolor у tbody | Атрибуты устарели | Свойства text-align, vertical-align и background |