Тег tr (от английского table row, «строка таблицы») задаёт одну горизонтальную строку: внутри записывают ячейки td с данными и th с заголовками. Число колонок таблицы определяют ячейки всех строк вместе, а сам тег tr почти ничего не рисует и не имеет собственных атрибутов.
Как строки и ячейки складываются в таблицу, объясняет урок «Строки tr и ячейки td». Здесь разберём остальное: какие атрибуты у строки устарели, а каких у неё нет совсем, какие стили на неё действуют, как выделять строки и собирать их в карточки, как не разорвать строку при печати и как работать с ней из скрипта.
<table>
<tr>
<th scope="col">Чай</th>
<th scope="col">Цена</th>
</tr>
<tr>
<td>Сенча</td>
<td>100</td>
</tr>
</table>
В этой статье
- Тег tr в HTML: что это и что записывают внутри
- Где записывать tr: родители, опущенные теги и строки вне таблицы
- Атрибуты tr: что устарело и чего не существует
- Стили tr: фон, рамка, высота и наследование
- Зебра, наведение и выбор строки tr в CSS
- Строки tr карточками на узком экране
- Печать tr: как не разорвать строку между страницами
- Тег tr и JavaScript: rowIndex, cells и insertCell
- Частые ошибки с тегом tr
Тег tr в HTML: что это и что записывают внутри
Внутри tr записывают только ячейки: td для данных и th для заголовков, в любом сочетании. Для скринридера строка получает роль row, а ячейки th со scope становятся заголовками колонки или строки. Свои атрибуты у тега не определены, допустимы только глобальные: id, class, hidden, tabindex, data-*. Закрывающий тег можно опустить, если следом идёт другая строка или кончается секция.
Сетку таблицы задаёт самая широкая строка: короткие строки остаются с пустым местом справа, а строка длиннее первой добавляет таблице лишнюю колонку. Одну ячейку на всю ширину делает colspan. Валидатор Nu считает строку без ячеек ошибкой, а про строки разной длины только предупреждает.
| Запись | Что получается | Пример записи |
|---|---|---|
td в tr | Ячейка с данными | <td>100</td> |
th в tr | Заголовок столбца или строки | <th scope="col">Цена</th> |
| Строка короче остальных | Справа пустое место, валидатор предупреждает | <tr><td>Улун</td></tr> |
| Строка длиннее первой | Таблица получает лишнюю колонку, валидатор предупреждает | <tr><td>Сенча</td><td>100</td><td>новинка</td></tr> |
Строка с colspan | Одна ячейка на всю ширину, замечаний нет | <tr><td colspan="2">Скидка</td></tr> |
tr без ячеек | Пустая строка, валидатор считает её ошибкой | <tr></tr> |
Пример: ячейки в строке
Выберите запись: вверху виден код, под ним — что получилось на экране.
Где записывать tr: родители, опущенные теги и строки вне таблицы
Родителем tr служит одна из секций: thead, tbody или tfoot. Стандарт разрешает записывать строки и прямо в table, но браузер всё равно создаёт tbody сам, поэтому селектор table > tr ничего не находит, а table > tbody > tr находит. Как браузер строит секции при опущенных тегах, описано на странице tbody.
Закрывающие </tr> и </td> можно опустить: границы определяются по следующему тегу. Вне таблицы tr теряется: браузер отбрасывает теги и оставляет только текст. Текст и div прямо в строке выносятся перед таблицу. Вложенный tr не создаёт таблицу в таблице, а становится следующей строкой: для вложенной таблицы в ячейку записывают полный тег table.
| Как записано | Что делает браузер | Пример записи |
|---|---|---|
tr в tbody | Оставляет как есть | <tbody><tr>...</tr></tbody> |
tr прямо в table | Создаёт tbody сам | <table><tr>...</tr></table> |
Опущены </tr> и </td> | Разделяет строки и ячейки по следующему тегу | <tr><td>Сенча<td>100<tr><td>Улун |
tr внутри tr | Делает вложенную строку соседней | <tr><td>Сенча<tr><td>Улун |
Текст или div в tr | Выносит их перед таблицу | <tr>Итого<td>Сенча</td></tr> |
tr вне table | Отбрасывает теги, остаётся текст | <div><tr><td>Сенча</td></tr></div> |
Пример: как браузер разбирает строки
Выберите запись: вверху виден код, под ним — что построил браузер.
Допустимые места записи перечислены в описании элемента в стандарте HTML.
Атрибуты tr: что устарело и чего не существует
Собственных атрибутов у tr в стандарте нет. Старые align, valign, bgcolor и height считаются устаревшими: Chrome их выполняет, а валидатор Nu выдаёт предупреждение. Атрибутов width и colspan у tr не существует совсем: валидатор считает их ошибкой, а браузер их игнорирует. Ширину задают ячейкам, а колонки объединяет colspan у td и th.
Из глобальных атрибутов строке полезны hidden (скрывает строку), tabindex (делает строку доступной с клавиатуры), aria-selected (отмечает выбранную строку) и data-* (хранит данные для скрипта). Атрибут role="row" писать не нужно: роль у строки есть, а валидатор считает такую запись ошибкой.
| Атрибут | Что получается | Пример записи |
|---|---|---|
bgcolor | Устарел: строка закрашивается, валидатор предупреждает | Свойство background |
align | Устарел: текст выравнивается, валидатор предупреждает | Свойство text-align |
valign | Устарел: ячейки выравниваются по вертикали, валидатор предупреждает | Свойство vertical-align |
height | Устарел: задаёт минимальную высоту строки, валидатор предупреждает | Свойство height |
width | Не существует: валидатор считает ошибкой, браузер игнорирует | Ширина у ячеек: td { width: 110px; } |
colspan | Не существует: валидатор считает ошибкой, браузер игнорирует | <td colspan="2">Скидка</td> |
hidden | Допустим: скрывает строку | <tr hidden> |
tabindex, aria-selected, data-* | Допустимы | <tr tabindex="0" aria-selected="true" data-id="5"> |
role="row" | Валидатор считает ошибкой: роль у строки уже есть | Убрать атрибут |
Пример: атрибуты строки и их замена
Выберите атрибут: вверху виден код, под ним — что сделал браузер и что сказал бы валидатор.
tr {
background: #ffd9a8;
text-align: right;
vertical-align: bottom;
height: 100px;
}
td, th {
width: 110px;
}
Стили tr: фон, рамка, высота и наследование
Строка в CSS ведёт себя не как блок: у неё есть фон, высота и наследуемые свойства, но рамка, скругление, отступы и ширина либо зависят от режима таблицы, либо не действуют вовсе. Цвет, размер шрифта, text-align и vertical-align у tr наследуют и td, и th, так же как у секций, о чём подробнее на странице thead. Жирность у th задана браузером, и от строки она не меняется.
Свойство у tr | Что получается | Пример записи |
|---|---|---|
background | Закрашивает ячейки строки, промежутки border-spacing остаются без фона; фон td перекрывает фон строки | tr { background: #ffd9a8; } |
border | Рисуется только при border-collapse: collapse | tr { border-bottom: 1px solid #b8b0a0; } |
border-radius | Не действует: скругляют первую и последнюю ячейки | td:first-child { border-radius: 8px 0 0 8px; } |
padding, margin | Не действуют: отступы задают ячейкам | td { padding: 8px; } |
width | Не действует: ширину задают ячейкам | td { width: 110px; } |
height | Задаёт минимум: ячейка с большим содержимым растянет строку | tr { height: 80px; } |
outline, box-shadow | Работают, размеры не меняются | tr { box-shadow: inset 0 0 0 3px #c0292e; } |
transform | Работает: строка сдвигается целиком | tr:hover { transform: translateX(8px); } |
color, text-align, font-weight | Первые два наследуют td и th, жирность меняется только у td: у th она задана браузером | tr { text-align: right; } |
Пример: что действует на строку
Выберите свойство и при желании включите border-collapse: collapse: результат виден сразу.
table {
border-collapse: collapse;
}
tr {
height: 40px;
vertical-align: middle;
border-bottom: 1px solid #b8b0a0;
}
td:first-child {
border-radius: 8px 0 0 8px;
}
td:last-child {
border-radius: 0 8px 8px 0;
}
Зебра, наведение и выбор строки tr в CSS
Чередование цветов, подсветка под курсором и выбор строки записывают селекторами на tr. Нумерация :nth-child() начинается заново в каждой секции: шапка не сдвигает полосы тела, а в таблице из нескольких tbody полосы в каждой группе начинаются с начала. Чем отличается счёт у самих секций tbody, описано на странице tbody.
| Запись | Что получается | Пример записи |
|---|---|---|
tbody tr:nth-child(even) | Чётные строки каждого tbody | tbody tr:nth-child(even) { ... } |
tr:hover | Строка под курсором; работает, если у ячеек нет своего фона | tr:hover { background: #ffd9a8; } |
tr:hover td | Подсветка видна и при фоне у ячеек | tr:hover td { background: #ffd9a8; } |
tr:has(input:checked) | Строка с отмеченным флажком | tr:has(input:checked) { ... } |
tr:focus-within | Строка, в которой находится фокус | tr:focus-within { outline: 3px solid; } |
tr:last-child | Последняя строка каждой секции | tr:last-child { border-bottom: 0; } |
tr[aria-selected="true"] | Выбранная строка: атрибут переключает скрипт | tr[aria-selected="true"] { ... } |
Пример: полосы, наведение и выбор строки
Выберите правило: вверху виден код, под ним таблица, на которой правило можно проверить мышью и клавиатурой.
Строке, которую выбирают с клавиатуры, задают tabindex="0": тогда она получает фокус клавишей Tab, а скрипт может переключать aria-selected по Enter.
tbody tr:nth-child(even) {
background: #f5f1ea;
}
tr:hover td {
background: #ffe9c7;
}
tr[aria-selected="true"] td {
background: #ffd9a8;
}
Строки tr карточками на узком экране
На узком экране широкую таблицу перестраивают: строкам и ячейкам задают display: block, и каждая строка становится карточкой. Названия колонок берут из атрибута data-label у ячейки и выводят псевдоэлементом ::before, а шапку прячут не через display: none, а так, чтобы она осталась для скринридера.
У такой раскладки есть ловушка: как только строке задан display, атрибут hidden перестаёт её скрывать, потому что правило автора перекрывает стиль браузера. Скрытая строка появляется снова, пока не добавить tr[hidden] { display: none; }.
| Запись | Результат | Пример записи |
|---|---|---|
tr, td с display: block | Ячейки встают столбиком, строки отделяются друг от друга | tr, td { display: block; } |
td::before с data-label | Название колонки выводится перед значением | td::before { content: attr(data-label); } |
Шапка с display: none | Названия колонок пропадают и для скринридера | thead { display: none; } |
| Шапка с обрезкой по размеру | Шапка невидима, но остаётся в дереве доступности (проверено в Chrome) | thead { position: absolute; clip: rect(0 0 0 0); } |
tr с display: flex | Ячейки выстраиваются в ряд по ширине содержимого | tr { display: flex; } |
display: flex и hidden | Скрытая строка снова видна | <tr hidden> |
tr[hidden] с display: none | hidden снова скрывает строку | tr[hidden] { display: none; } |
Пример: таблица и карточки
Выберите вариант: вверху виден код, под ним таблица. Одна строка в таблице скрыта атрибутом hidden.
@media (max-width: 600px) {
thead {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0 0 0 0);
}
tr, td {
display: block;
}
td::before {
content: attr(data-label);
font-weight: 600;
}
tr[hidden] {
display: none;
}
}
<tr>
<td data-label="Чай">Сенча</td>
<td data-label="Цена">100</td>
<td data-label="Вес">50 г</td>
</tr>
Печать tr: как не разорвать строку между страницами
При печати строка таблицы может разорваться: верх ячеек остаётся на одной странице, а низ переходит на следующую. В пробной таблице из 30 строк по шесть строк текста в ячейке на листах A4 разорвались две строки из тридцати. Правило break-inside: avoid у tr оставляет строку целой: если она не помещается, браузер переносит её на следующую страницу. То же правило для целой группы строк описано на странице tbody, а повтор шапки и подвала на каждой странице — на странице thead.
@media print {
tr {
break-inside: avoid;
}
}
| Запись | Результат при печати | Пример записи |
|---|---|---|
| Без правил | Строка на границе страниц может разорваться | <tr>...</tr> |
tr с break-inside: avoid | Строка переносится на следующую страницу целиком | tr { break-inside: avoid; } |
Результат видно в предпросмотре печати: Ctrl + P.
Тег tr и JavaScript: rowIndex, cells и insertCell
Строка в скрипте представлена интерфейсом HTMLTableRowElement. Свойство rowIndex возвращает номер строки в таблице в порядке «шапка, тело, подвал», sectionRowIndex — номер внутри секции, cells — все ячейки строки, и td, и th. Метод insertCell() добавляет ячейку, deleteCell() удаляет. У строки вне таблицы rowIndex равен -1.
| Запись | Что делает | Пример записи |
|---|---|---|
row.rowIndex | Возвращает номер строки в таблице | row.rowIndex |
row.sectionRowIndex | Возвращает номер строки внутри секции | row.sectionRowIndex |
row.cells | Возвращает ячейки td и th строки | row.cells[0] |
row.insertCell(-1) | Добавляет в конец строки ячейку td | row.insertCell(-1) |
row.deleteCell(-1) | Удаляет последнюю ячейку | row.deleteCell(-1) |
row.cloneNode(true) | Копирует строку вместе с ячейками | row.cloneNode(true) |
e.target.closest("tr") | Находит строку, в которой произошёл клик | e.target.closest("tr") |
Пример: методы строки
Выберите действие: вверху виден код, под ним таблица и результат, который считает сама страница.
table.addEventListener('click', (event) => {
const row = event.target.closest('tr');
if (!row || !table.contains(row)) return;
for (const other of table.rows) {
other.removeAttribute('aria-selected');
}
row.setAttribute('aria-selected', 'true');
});
Метод insertCell() всегда создаёт td, поэтому ячейку-заголовок создают через document.createElement('th'). Метод table.deleteRow(-1) удаляет последнюю строку всей таблицы, а при наличии подвала это строка подвала: строки тела удаляют методом tbody.deleteRow(-1). Добавление строк в тело разобрано на странице tbody, а создание подвала — на странице tfoot.
Частые ошибки с тегом tr
Типичные ошибки со строками таблицы и как их исправить.
| Ошибка | Что происходит | Как исправить |
|---|---|---|
width или colspan у tr | Браузер игнорирует, валидатор считает ошибкой | Ширину задавать ячейкам, колонки объединять через colspan у td |
Пустой tr для промежутка между строками | Валидатор считает строку без ячеек ошибкой | table { border-spacing: 0 8px; } |
| Разное число ячеек в строках | Справа остаётся пустое место, валидатор предупреждает | Дописать ячейки или использовать colspan |
border у tr без collapse | Рамка не рисуется | Включить border-collapse: collapse или задать границы ячейкам |
border-radius, padding, margin у tr | Свойства не действуют | Задавать их ячейкам |
Атрибуты bgcolor, align, valign, height | Устарели, валидатор предупреждает | Свойства background, text-align, vertical-align, height |
Текст или div прямо в tr | Браузер выносит их перед таблицу | Записывать в ячейку td |
tr вне table | Теги пропадают, остаётся текст | Записывать строки внутри table |
table > tr в CSS или скрипте | Ничего не находит: браузер добавил tbody | table > tbody > tr |
tr:hover при фоне у ячеек | Подсветки не видно | tr:hover td { background: #ffe9c7; } |
display: flex у tr без правила для hidden | Скрытая строка снова видна | tr[hidden] { display: none; } |
| Строка разрывается при печати | Верх строки на одной странице, низ на другой | tr { break-inside: avoid; } |
insertCell() в строке шапки | Создаётся td, а не th | document.createElement('th') |
table.deleteRow(-1) при подвале | Удаляется строка подвала | tbody.deleteRow(-1) |
role="row" у tr | Валидатор считает ошибкой: роль уже есть | Убрать атрибут |