Как правильно размечать таблицы и списки в 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>?
  • Как таблицы адаптируют под мобильные экраны без потери семантики?