Тег th в HTML: что это, чем отличается от td, scope и headers

Тег th (от английского table header, «заголовок таблицы») создаёт заголовок таблицы: название колонки, строки или целой группы строк. На экране такая ячейка отличается от td только жирным шрифтом и центрированием, а главное отличие скрыто: скринридер при переходе по ячейкам сообщает заголовок, и по нему человек понимает, что за число он слышит.

Основы тега и атрибута scope разобраны в уроке «Тег th и атрибут scope: заголовки колонок и строк таблицы». Здесь разберём остальное: как браузер определяет роль заголовка без scope, чем headers отличается от scope, как собрать многоуровневую шапку, почему выравнивание th не сдвигается от некоторых значений родителя, как закрепить шапку вместе с колонкой и как сделать заголовки сортируемыми.

HTML
<table>
  <tr>
    <th scope="col">Чай</th>
    <th scope="col">Цена</th>
  </tr>
  <tr>
    <th scope="row">Сенча</th>
    <td>100</td>
  </tr>
</table>

Тег th в HTML: что это и чем отличается от td

Ячейка th записывается только внутри строки tr. Закрывающий </th> можно опустить: ячейка кончается там, где начинается следующая th или td. Если записать th прямо в table, валидатор Nu сообщит об ошибке, а браузер сам достроит tbody и tr. Внутри допустимы абзацы, ссылки, кнопки и другие элементы потока, кроме заголовков, header, footer и секций.

Роль ячейки Chrome определяет по положению. th в строке шапки становится заголовком колонки (роль columnheader), а первая th в строке тела или подвала — заголовком строки (роль rowheader). Поэтому подмена не проходит ни в одну сторону: td со стилями заголовка остаётся ячейкой данных, а th, записанная ради жирного шрифта в ячейке с числом, превращает данные в заголовок.

ЗаписьЧто получаетсяПример записи
th в строке шапкиЗаголовок колонки, жирный текст по центру<th>Цена</th>
Первая th в строке телаЗаголовок строки<tr><th>Сенча</th><td>100</td></tr>
td со стилями заголовкаВыглядит как заголовок, для скринридера это данныеtd { font-weight: 700; }
th вместо td ради жирностиДанные становятся заголовком<tr><td>Сенча</td><th>100</th></tr>
Пустая th в углу таблицыДопустима: закрывает угол над заголовками строк<th></th>
th прямо в tableБраузер достраивает tbody и tr, валидатор считает ошибкой<table><th>Цена</th></table>

Пример: чем th отличается от td

Результат

Выберите запись: вверху виден код, под ним — что получилось на экране.

Допустимое содержимое и места записи описаны в стандарте HTML.

Атрибут scope у th: col, row, colgroup и rowgroup

Атрибут scope говорит, на какие ячейки действует заголовок. Значение col задаёт колонку, row — строку, colgroup и rowgroup — группу колонок или строк: их записывают у th, которая охватывает несколько колонок или строк через colspan или rowspan. Без атрибута браузер решает по положению ячейки, и для простых таблиц этого хватает.

Явный scope полезен там, где положение может подсказать неверно: таблица с заголовками и в шапке, и в первой колонке, угловая ячейка, итоговая строка в подвале. Допустимы только четыре значения. Запись scope="auto", хотя так называется состояние по умолчанию в стандарте, валидатор считает ошибкой, как и scope="column" или пустое значение. У td атрибут scope устарел и ничего не меняет: ячейка остаётся ячейкой данных.

ЗаписьЧто получаетсяПример записи
scope="col"Заголовок относится к ячейкам колонки под ним<th scope="col">Цена</th>
scope="row"Заголовок относится к ячейкам строки правее него<th scope="row">Сенча</th>
scope="colgroup"Заголовок относится к группе колонок<th scope="colgroup" colspan="2">Цена</th>
scope="rowgroup"Заголовок относится к группе строк<th scope="rowgroup" rowspan="2">Зелёный</th>
Без scopeОхват по положению: в шапке колонка, первая в строке тела строка<th>Цена</th>
scope="auto", "column", пустоеНедопустимы: валидатор считает ошибкой<th scope="column">Цена</th>
scope у tdУстарел и игнорируется<td scope="row">Сенча</td>

Пример: на какие ячейки действует scope

Результат

Выберите значение: вверху виден код, под ним таблица. Подсвечены ячейки, к которым относится заголовок по правилам стандарта.

Атрибут headers и id: сложные заголовки th

Если у ячейки несколько заголовков разных уровней, scope их не опишет. Тогда каждой th задают id, а ячейке данных в атрибуте headers перечисляют через пробел все заголовки, к которым она относится. Типичный случай: шапка с ячейкой Цена, которая занимает две колонки, и под ней подзаголовки Опт и Розница. Ячейка со значением должна указать оба уровня: headers="pr opt".

Правила строгие. В списке допустимы только id ячеек th из той же таблицы: ссылка на td или на несуществующий id считается ошибкой, повторяющийся id тоже. Если указан один уровень, со вторым ячейка не связана. При наличии headers по стандарту связь строят по этому списку, а не по положению ячейки. Как headers выглядит на ячейке данных, видно и на странице td.

ЗаписьЧто получаетсяПример записи
id у thМетка для ссылки из headers<th id="pr">Цена</th>
headers с обоими уровнямиЯчейка связана с Цена и Опт<td headers="pr opt">90</td>
headers с одним уровнемВерхний уровень теряется<td headers="opt">90</td>
headers на tdВалидатор считает ошибкой<td headers="t1">90</td>
headers на несуществующий idВалидатор считает ошибкой<td headers="net">90</td>
headers у thДопустим: заголовок второго уровня ссылается на верхний<th id="opt" headers="pr">Опт</th>

Пример: ячейка и её заголовки

Результат

Выберите запись и нажмите на любую ячейку тела: подсветятся заголовки, на которые она ссылается.

HTML
<tr>
  <th rowspan="2" id="ch">Чай</th>
  <th colspan="2" id="pr">Цена</th>
</tr>
<tr>
  <th id="opt">Опт</th>
  <th id="rz">Розница</th>
</tr>
<tr>
  <td headers="ch">Сенча</td>
  <td headers="pr opt">90</td>
  <td headers="pr rz">100</td>
</tr>

Атрибуты th: abbr, устаревшие и что нельзя внутри

Кроме scope, headers, colspan и rowspan, у th есть abbr: сокращённое название заголовка, которое скринридер может читать вместо длинного. В Chrome имя ячейки для скринридера остаётся полным текстом, поэтому abbr служит подсказкой, а не заменой короткого текста в самой ячейке. У td этот атрибут устарел.

Устарели атрибуты width, height, align, valign, bgcolor, nowrap, а также axis (его заменил scope), char и charoff. Chrome их выполняет, валидатор предупреждает. Вместо них пишут свойства CSS. Роль писать не нужно: любой role у th, даже columnheader, валидатор считает ошибкой. Допустимы глобальные id, class, tabindex, hidden, а также aria-sort: на th он разрешён, а на td нет.

ЗаписьЧто получаетсяПример записи
abbr у thДопустим: сокращённое название для скринридера<th abbr="Цена">Цена за 100 г</th>
abbr, scope у tdУстарели, валидатор предупреждаетУбрать атрибут
width, height, align, valignУстарели: применяются, валидатор предупреждаетСвойства width, height, text-align, vertical-align
bgcolor, nowrapУстарели: применяются, валидатор предупреждаетСвойства background, white-space
axis, char, charoffУстарели, валидатор предупреждаетДля axis писать scope
role="columnheader"Валидатор считает ошибкой: роль у th уже естьУбрать атрибут
aria-sort у thДопустим: отмечает отсортированную колонку<th aria-sort="ascending">
colspan="0"Валидатор считает ошибкой<th colspan="2">Цена</th>
rowspan="0"Допустим: до конца секции<th rowspan="0">Чай</th>
p, button внутри thДопустимы<th><button type="button">Цена</button></th>
h3, header, section внутри thВалидатор считает ошибкой<th><h3>Цена</h3></th>

Многоуровневая шапка th: colspan и rowspan

Ступенчатая шапка из двух строк собирается так: верхняя ячейка с colspan накрывает подзаголовки, а ячейка слева с rowspan занимает обе строки шапки. Если забыть rowspan, подзаголовки второй строки сдвигаются влево под колонку первой ячейки, а данные расходятся с шапкой. Значение rowspan="0" растягивает ячейку до конца секции, в тело оно не заходит.

То же работает и в тело таблицы: th с rowspan и scope="rowgroup" задаёт название группы строк, а в каждой строке остаётся своя th со scope="row". Пределы значений colspan и rowspan описаны на странице td: у th они те же.

ЗаписьЧто получаетсяПример записи
colspan над подзаголовкамиЯчейка шириной в две колонки<th colspan="2">Цена</th>
rowspan у левой ячейкиЯчейка высотой в две строки шапки<th rowspan="2">Чай</th>
Забыт rowspanПодзаголовки сдвигаются под первую колонку<th>Чай</th><th colspan="2">Цена</th>
rowspan="0"Ячейка до конца секции thead<th rowspan="0">Чай</th>
colspan больше числа колонокЛишние колонки без ширины, валидатор считает ошибкой<th colspan="5">Цена</th>
rowspan и scope="rowgroup"Заголовок группы строк<th rowspan="2" scope="rowgroup">Зелёный</th>

Пример: шапка из двух уровней

Результат

Выберите запись: вверху виден код, под ним таблица и размеры ячеек, которые считает сама страница.

Стили th: выравнивание, жирность и выбор th в CSS

Жирность и центрирование заданы правилом самого браузера для th, а не наследуются от строки. Из этого вытекают две ловушки. Значения left или right у tr, thead и table сдвигают заголовки, а text-align: start у родителя нет: оно совпадает с начальным значением, и заголовок остаётся по центру. Чтобы выровнять заголовки, правило пишут на самой th: th { text-align: start; } даёт ожидаемый результат.

То же с жирностью: font-weight: 400 у tr шрифт th не меняет, нужно правило на самой ячейке. Наследование остальных свойств строки подробно описано на странице tr. Шапку, колонку заголовков и ячейки данных различают селекторы thead th, tbody th и th[scope="row"]: во втором и третьем случае правило не затрагивает шапку.

ЗаписьЧто получаетсяПример записи
Без правилЖирный шрифт, выравнивание по центруth { }
text-align: left у trЗаголовки выровнены влевоtr { text-align: left; }
text-align: start у trНе действует: заголовки остаются по центруtr { text-align: start; }
font-weight: 400 у trНе действует: заголовки жирныеtr { font-weight: 400; }
text-align: start у thВыравнивание по началу строкиth { text-align: start; }
thead th, tbody thОтдельный вид для шапки и колонки заголовковtbody th { text-align: left; }
th[scope="row"]Выбор заголовков строк по атрибутуth[scope="row"] { background: #cfe8e4; }

Пример: что действует на th

Результат

Выберите правило: вверху виден код, под ним таблица и значения, которые считает сама страница.

CSS
th {
  text-align: start;
  font-weight: 600;
}
thead th {
  background: #ffd9a8;
}
th[scope="row"] {
  background: #cfe8e4;
}

Длинные и вертикальные заголовки th в узкой таблице

Заголовок без пробелов задаёт колонке минимальную ширину, и таблица выходит за свой блок. Ширину возвращает overflow-wrap: anywhere: слово переносится по буквам. Другой путь: оставить заголовок в одну строку правилом white-space: nowrap и дать таблице блок с overflow-x: auto, тогда прокручивается блок, а не страница. Остальные приёмы с длинным словом и многоточием описаны на странице td.

Для узких колонок с длинными названиями заголовок поворачивают: writing-mode: vertical-rl делает текст вертикальным, и ячейка сужается до ширины одной строки текста. Текст читается сверху вниз. Если нужно снизу вверх, добавляют transform: rotate(180deg), но размер ячейки при этом остаётся прежним.

ЗаписьЧто получаетсяПример записи
Слово без пробелов в thКолонка и таблица растягиваются<th>Среднесуточнаякалорийность</th>
overflow-wrap: anywhereСлово переносится по буквамth { overflow-wrap: anywhere; }
white-space: nowrap и блокЗаголовок в одну строку, прокрутка в блокеth { white-space: nowrap; }
writing-mode: vertical-rlВертикальный текст, ячейка ужеth { writing-mode: vertical-rl; }
transform: rotate(180deg)Текст снизу вверх, размер прежнийth { transform: rotate(180deg); }

Пример: длинные и повёрнутые заголовки

Результат

Выберите правило: вверху виден код, под ним таблица в блоке шириной до 180 px и размеры, которые считает сама страница.

Закрепление шапки и колонки th: угловая ячейка

В большой таблице закрепляют сразу шапку и колонку заголовков: thead th получает position: sticky и top: 0, tbody th получает position: sticky и left: 0, а таблицу оборачивают в блок с overflow: auto и фиксированной высотой. Закрепление одной шапки и повтор шапки на каждой печатной странице описаны на странице thead, а закрепление подвала с итогами — на странице tfoot.

У связки есть ловушка с углом. Колонка заголовков стоит в разметке позже шапки и при прокрутке ложится поверх неё: слева вверху вместо названия угла видна первая th колонки. Угловой ячейке задают left: 0 и z-index: 2. Только left: 0 перекрытия не лечит, а z-index у всей шапки без закрепления угла оставляет угол уехавшим. Фон у закреплённых ячеек обязателен, иначе содержимое просвечивает. Что происходит с границей закреплённой ячейки при border-collapse: collapse, показано на странице td.

ЗаписьЧто получаетсяПример записи
Закреплена только шапкаУгловая ячейка уезжает вместе с колонкамиthead th { position: sticky; top: 0; }
Закреплены шапка и колонкаКолонка перекрывает шапку слева вверхуtbody th { position: sticky; left: 0; }
Угол с left: 0 без z-indexПерекрытие остаётсяthead th:first-child { left: 0; }
z-index: 1 у всей шапкиКолонка под шапкой, но угол уехалthead th { z-index: 1; }
Угол с left: 0 и z-index: 2Угол на месте, шапка и колонка не пересекаютсяthead th:first-child { left: 0; z-index: 2; }

Пример: закрепление шапки и колонки

Результат

Выберите правило: вверху виден код, под ним блок с прокруткой, уже сдвинутый вниз и вправо.

CSS
thead th {
  position: sticky;
  top: 0;
  background: #fde;
}
tbody th {
  position: sticky;
  left: 0;
  background: #def;
}
thead th:first-child {
  left: 0;
  z-index: 2;
}

Тег th и JavaScript: scope, createElement и сортировка

Ячейка th в скрипте представлена тем же интерфейсом HTMLTableCellElement, что и td. Свойство scope принимает только допустимые значения: неверное оставляет в атрибуте написанное, но при чтении возвращает пустую строку, а регистр значения не важен. Свойства abbr и headers читаются как строки. Свойство cells возвращает th и td вместе, подробнее о нём на странице tr.

Метод insertCell() всегда создаёт td, даже в строке шапки, поэтому ячейку th создают через document.createElement('th') и добавляют методом append(). Чтобы превратить готовую строку из td в заголовки, каждую ячейку заменяют методом replaceWith().

ЗаписьЧто делаетПример записи
th.scope = "col"Записывает значение в атрибутth.scope = 'col'
th.scope = "column"Атрибут записан, чтение возвращает пустую строкуth.scope = 'column'
row.insertCell(-1) в шапкеСоздаёт td, а не throw.insertCell(-1)
createElement("th")Создаёт ячейку thdocument.createElement('th')
td.replaceWith(th)Заменяет ячейку данных на заголовокtd.replaceWith(th)
row.cellsВозвращает th и td строки вместеrow.cells[0].tagName
e.target.closest("th")Находит заголовок, на который нажалиe.target.closest('th')

Пример: свойства и создание th

Результат

Выберите действие: вверху виден код, под ним таблица и результат, который считает сама страница.

Заголовки колонок часто делают сортируемыми. Внутрь th помещают button, чтобы заголовок получал фокус и срабатывал по Enter, а у отсортированной колонки добавляют aria-sort: ascending, descending или none. Значение, отличное от none, задают только одной колонке, у остальных оно none. Без button заголовок мышью сортирует, но с клавиатуры до него не добраться.

Пример: сортировка по заголовку

Результат

Нажмите на заголовок колонки: строки отсортируются, повторное нажатие меняет направление.

JavaScript
table.addEventListener('click', (event) => {
  const th = event.target.closest('th');
  if (!th) return;
  for (const other of table.tHead.rows[0].cells) {
    other.setAttribute('aria-sort', 'none');
  }
  th.setAttribute('aria-sort', 'ascending');
  // затем строки тела сортируются по колонке th.cellIndex
});

Частые ошибки с тегом th

Типичные ошибки с тегом th и как их исправить.

ОшибкаЧто происходитКак исправить
td со стилями вместо thЗаголовок виден глазами, скринридер читает данныеЗаписывать заголовки в th
th ради жирности в ячейке данныхДанные становятся заголовкомОставить td, жирность задать в CSS
scope="auto", "column" или пустойВалидатор считает ошибкой, свойство возвращает пустую строкуПисать col, row, colgroup или rowgroup
scope у tdАтрибут устарел и ничего не меняетПеренести в th
headers на td или несуществующий idВалидатор считает ошибкойСсылаться на id ячеек th этой же таблицы
headers с одним уровнемВерхний заголовок теряетсяУказывать все уровни через пробел
role="columnheader" у thВалидатор считает ошибкойУбрать атрибут
Атрибуты width, align, valign, bgcolorУстарели, валидатор предупреждаетСвойства width, text-align, vertical-align, background
h3, header или section внутри thВалидатор считает ошибкойЗаписывать текст, абзац или кнопку
Забыт rowspan в двухуровневой шапкеПодзаголовки сдвигаются под первую колонкуДобавить rowspan="2" левой ячейке
text-align: start у trЗаголовки остаются по центруth { text-align: start; }
font-weight: 400 у trЗаголовки остаются жирнымиth { font-weight: 400; }
Закреплены шапка и колонка без углаКолонка перекрывает шапку слева вверхуleft: 0; z-index: 2;
Длинное слово в thТаблица шире блокаoverflow-wrap: anywhere
insertCell() в строке шапкиСоздаётся td, а не thdocument.createElement('th')
Сортируемая th без buttonНет фокуса с клавиатурыПоместить в th кнопку и добавить aria-sort
Категория
Поделиться в социальных сетях

Отправить комментарий

0 Комментарии

Отправить комментарий (0)

#buttons=(Хорошо) #days=(20)

Мы используем файлы cookie для улучшения работы сайта. Подробнее
Хорошо
×