Как правильно размечать таблицы и списки в HTML?
JuniorHTML #html #tables #lists
Вопрос
Как выглядит корректная семантическая разметка таблицы (thead, tbody, th с scope) и чем отличаются друг от друга три типа списков — <ul>, <ol> и <dl>?
Короткий ответ
Таблица должна разделять шапку (<thead>) и тело (<tbody>), а заголовочные ячейки — быть тегом <th> с атрибутом scope, указывающим, к чему они относятся (столбцу или строке) — это критично для скринридеров. Из списков: <ul> — маркированный (порядок не важен), <ol> — нумерованный (порядок важен), <dl> — список определений (пары термин/описание через <dt>/<dd>).
Подробный ответ
Таблицы:
<table>— контейнер.<caption>(первый дочерний элемент) — подпись/название таблицы, полезна и визуально, и для accessibility.<thead>— строка(и) заголовков столбцов.<tbody>— основные данные; может быть несколько<tbody>для логической группировки.<tfoot>— итоговая строка (суммы, итоги).<th>вместо<td>для заголовочных ячеек — это не просто жирный текст, а семантический маркер: скринридер объявляет соответствующий заголовок при перемещении по ячейкам данных.scope="col"—<th>является заголовком для всего столбца;scope="row"— для всей строки. Безscopeскринридер не может однозначно связать ячейку данных с нужным заголовком в сложных таблицах.- Для сложных таблиц с объединёнными ячейками используют
id/headersвместоscopeдля точного связывания.
Списки:
<ul>(unordered list) — маркированный список, когда порядок элементов не несёт смысла (список фич, тегов, ссылок в меню).<ol>(ordered list) — нумерованный список, когда порядок важен (шаги инструкции, рейтинг). Поддерживает атрибутыstartиreversed.<dl>(description list) — список пар "термин — описание":<dt>(term) и<dd>(description). Подходит для глоссариев, FAQ, метаданных (например, характеристик товара).- Списки можно вкладывать друг в друга (
<ul>внутри<li>) для многоуровневой структуры (например, дерево навигации). - Единственные валидные прямые дети
<ul>/<ol>— это<li>; текст напрямую внутри<ul>невалиден.
Пример
<table>
<caption>Цены на тарифы</caption>
<thead>
<tr>
<th scope="col">Тариф</th>
<th scope="col">Цена</th>
<th scope="col">Лимит запросов</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Basic</th>
<td>0 $</td>
<td>1 000</td>
</tr>
<tr>
<th scope="row">Pro</th>
<td>19 $</td>
<td>50 000</td>
</tr>
</tbody>
</table>
<!-- маркированный список -->
<ul>
<li>Быстрый старт</li>
<li>Поддержка TypeScript</li>
</ul>
<!-- нумерованный список -->
<ol>
<li>Установите зависимости</li>
<li>Настройте конфиг</li>
<li>Запустите dev-сервер</li>
</ol>
<!-- список определений -->
<dl>
<dt>SSR</dt>
<dd>Рендеринг страницы на сервере перед отправкой клиенту.</dd>
<dt>SSG</dt>
<dd>Генерация статических страниц во время сборки.</dd>
</dl>
Дополнительные вопросы
- В чём разница между
scopeи связкойid/headersдля сложных таблиц? - Когда стоит использовать
<dl>вместо обычного<ul>с двумя<span>внутри<li>? - Как таблицы адаптируют под мобильные экраны без потери семантики?