Таблицы в HTML и CSS: разбор типичных граблей вёрстки
Почему таблица ломает мобильную вёрстку, не держит sticky-заголовок и растягивает страницу горизонтально — и как это чинить без хаков.

Таблица — единственный блочный элемент, у которого ширина колонок зависит от содержимого сильнее, чем от ваших CSS-правил. Вы ставите width: 200px ячейке, а браузер её игнорирует и растягивает под длинную строку без пробелов. Ставите overflow: hidden — не работает. Пытаетесь сделать заголовок липким через position: sticky на <thead> — он прилипает не везде. Ниже — разбор того, почему таблицы ведут себя иначе, чем div-ы, и что с этим делать. Всё проверено в актуальных версиях Chrome, Firefox и Safari (2024 год).
Грабля первая: ширины колонок живут своей жизнью
По умолчанию у таблицы работает алгоритм table-layout: auto. Браузер сначала читает всё содержимое, считает минимальную и максимальную ширину каждой ячейки, а потом распределяет доступное пространство. Ваши width в этом режиме — не команда, а пожелание, которое перебивается более широким контентом.
Классический симптом: одна ячейка с длинным словом или URL без пробелов раздувает всю колонку, и таблица вылезает за пределы контейнера.
Лечится переключением на фиксированный алгоритм:
table {
table-layout: fixed; /* ширины берутся из первой строки, а не из контента */
width: 100%;
}
table td {
overflow-wrap: break-word; /* длинные слова переносятся, а не растягивают */
}
С table-layout: fixed браузер определяет ширины по первой строке (или по явным width на <col>/ячейках) и игнорирует остальное содержимое. Это быстрее для больших таблиц: рендер не ждёт загрузки всех строк. Плата — вы обязаны сами управлять ширинами, автоподгонки больше нет.
Где задавать ширину: col, th или td
При fixed ширину удобно вешать на элементы <col> внутри <colgroup> — так вы описываете колонки один раз, а не в каждой строке:
<table>
<colgroup>
<col style="width: 40%">
<col style="width: 20%">
<col style="width: 40%">
</colgroup>
<thead> ... </thead>
<tbody> ... </tbody>
</table>
Обратите внимание: на <col> из свойств применяются далеко не все — работают width, background, border и visibility: collapse. Отступы, выравнивание текста и прочее через <col> не задать, для этого нужны сами ячейки.
Грабля вторая: двойные и схлопывающиеся границы
У таблицы две модели границ, и по умолчанию включена не та, которую ждёшь. При border-collapse: separate (значение по умолчанию) границы соседних ячеек не сливаются, и вы видите двойные линии, а между ячейками зияет расстояние из border-spacing.
В 90% случаев нужно:
table {
border-collapse: collapse; /* соседние границы сливаются в одну линию */
}
Но у collapse есть неочевидное следствие: border-radius на ячейках и на самой таблице перестаёт работать. Скруглить углы таблицы с border-collapse: collapse напрямую нельзя — браузер игнорирует радиус. Если нужны скруглённые углы, оставляйте separate с border-spacing: 0 и рисуете границы аккуратно, чтобы не задваивались.
Правило, которое экономит часы отладки:collapse— когда нужна сетка из линий,separate— когда нужны скругления или отступы между ячейками. Совместить обе цели одним свойством нельзя.
Грабля третья: sticky-заголовок прилипает не везде
Логичный ход — сделать шапку липкой при прокрутке:
thead th {
position: sticky;
top: 0;
background: #fff; /* обязательно: иначе сквозь заголовок видно строки */
}
Здесь всплывают сразу две тонкости.
Первая — фон. Ячейки таблицы по умолчанию прозрачны. Без явного background прокручиваемые строки будут просвечивать сквозь прилипший заголовок. Фон вешают на сами <th>, а не на <thead> или <tr>.
Вторая — куда вешать sticky. Исторически position: sticky на <thead> и <tr> работал не во всех браузерах. В современных Chrome и Firefox это уже поддерживается, но самый совместимый вариант — вешать sticky прямо на <th> внутри шапки. Тогда прилипает каждая ячейка, и поведение предсказуемо во всех движках.
И третья ловушка, не связанная с таблицей напрямую: position: sticky отсчитывает top от ближайшего предка-скролл-контейнера. Если родитель имеет overflow: hidden или overflow: auto, липкость сломается — заголовок будет прилипать к краю этого контейнера, а не окна. Проверяйте цепочку родителей.
Грабля четвёртая: таблица ломает мобильную вёрстку
Таблица с шестью колонками физически не влезает в 360 CSS-пикселей телефона. Она либо растягивает всю страницу по горизонтали (появляется горизонтальный скролл у <body>), либо сжимает колонки в нечитаемую кашу.
Не пытайтесь ужать таблицу — дайте ей скроллиться внутри своего контейнера:
.table-wrap {
overflow-x: auto; /* горизонтальный скролл у обёртки, не у страницы */
-webkit-overflow-scrolling: touch;
}
.table-wrap table {
min-width: 600px; /* ниже этой ширины таблицу не сжимаем */
}
<div class="table-wrap">
<table> ... </table>
</div>
Таблица прокручивается внутри блока, а страница остаётся на месте. Альтернатива — перестроить таблицу в «карточки» через display: block для строк и псевдоэлементы с подписями, но это ломает семантику и доступность, поэтому применяйте осознанно и только когда данных немного.
Сравнение подходов к адаптивной таблице
| Подход | Плюс | Минус |
|---|---|---|
| Горизонтальный скролл обёртки | Сохраняет семантику и доступность, минимум кода | Пользователь не видит все колонки сразу |
| Перестройка в карточки | Всё видно без скролла | Ломает табличную семантику, сложнее для скринридеров |
| Скрытие второстепенных колонок | Компактно | Часть данных недоступна на мобильном |
Грабля пятая: colspan, rowspan и выравнивание
Объединённые ячейки — источник тихих багов. Если в одной строке сумма colspan не совпадает с числом колонок в других строках, браузер добьёт таблицу пустыми ячейками, и сетка поедет. Считайте колонки вручную: каждая строка должна давать одинаковую сумму с учётом colspan, а rowspan — «съедать» ячейку в следующей строке, которую вы не пишете.
Ещё одна мелочь: вертикальное выравнивание по умолчанию — vertical-align: middle. В таблицах с разной высотой строк текст в высоких ячейках болтается по центру. Если нужен верх, задавайте явно vertical-align: top для td.
Грабля шестая: zebra-подсветка и hover через nth-child
Чередование фона строк делают через :nth-child, и тут ошибаются с фоном при sticky-заголовке и с приоритетом:
tbody tr:nth-child(even) {
background: #f6f6f6;
}
tbody tr:hover {
background: #eef4ff; /* hover перебивает зебру, порядок правил важен */
}
Если hover объявлен выше зебры при равной специфичности, чётные строки при наведении не подсветятся. Держите :hover ниже. И помните, что фон, заданный на <td>, перекроет фон строки — при коллизии проверяйте, на каком уровне висит цвет.
Если сетка всё равно едет: чек-лист отладки
- Откройте инструменты разработчика и посмотрите вычисленное
table-layout. Еслиauto, а ширины игнорируются — переключите наfixed. - Найдите ячейку с самым длинным неразрывным содержимым (URL, хеш) и добавьте
overflow-wrap: break-word. - Проверьте, совпадает ли сумма
colspanво всех строках с числом колонок. - Если появился горизонтальный скролл у страницы — оберните таблицу в блок с
overflow-x: auto. - Если sticky не прилипает — проверьте
overflowу всех родителей и наличиеbackgroundу ячеек шапки.
Что делать дальше
Соберите себе базовый набор правил для таблиц (table-layout: fixed, border-collapse: collapse, обёртка со скроллом) в один партиал и переиспользуйте — половина граблей отпадает на старте. Для доступности проверьте, что у таблиц с данными есть <caption> и корректные <th scope="col"> / scope="row": скринридеры без этого не свяжут ячейку с заголовком. Актуальные детали поведения свойств сверяйте в MDN — таблицы одна из немногих областей, где поведение движков всё ещё расходится в мелочах.
Разбор научной статьи: Сложный текст — понятным языком, с терминами
Спросить за 12 ₽Источники: MDN: table-layout, MDN: border-collapse
Частые вопросы
Почему width на ячейке не работает?
Почему не работает border-radius на таблице?
sticky-заголовок прилипает, но строки просвечивают сквозь него. Что не так?
А если у меня старый браузер и sticky на thead не работает?
Как убрать горизонтальный скролл всей страницы из-за широкой таблицы?
Colspan сломал сетку, ячейки съехали. Как найти причину?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.