Адаптив таблиц под мобильные устройства

📂 Категория: CSS
🏷️ Теги:

Вариант 1: Классический горизонтальный скролл

Самый простой вариант, просто включаем горизонтальную прокрутку и немного уменьшаем размер шрифта на телефонах. Таблица сохраняет свою структуру, но помещается в контейнер с прокруткой.

Особенности:

  • Минимальное вмешательство в структуру на мобильных устройствах.

Плюсы:

  • Сохраняется нативная семантика <table>.
  • Нет дублирования контента.
  • Идеально для очень больших наборов данных (финансовые отчеты, лог-файлы).

Минусы:

  • Плохой UX: пользователю приходится постоянно скроллить влево-вправо, теряя из виду начало строки.
Заметка

В примерах можно сужать и расширять контейнер, чтобы посмотреть мобильную версию. Потяните за ярлычок справа снизу, как у textarea.

ФИО Дата рождения Должность Оклад, руб.
1 Белов Максим Артемович 22.10.1988 Технический директор 185 000
2 Мельникова Дарья Сергеевна 03.12.1995 Системный аналитик 140 000
3 Костин Андрей Николаевич 15.06.1991 DevOps-инженер 160 000
4 Соколова Алина Игоревна 28.01.1998 UI/UX дизайнер 115 000
5 Павлов Илья Владимирович 09.04.1985 Менеджер проектов 130 000
Показать HTML код
<div class="variant-1">
  <table>
    <thead>
      <tr>
        <th width="5%">№</th>
        <th width="40%">ФИО</th>
        <th width="15%">Дата рождения</th>
        <th width="20%">Должность</th>
        <th width="20%">Оклад, руб.</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>1</td>
        <td>Белов Максим Артемович</td>
        <td>22.10.1988</td>
        <td>Технический директор</td>
        <td>185 000</td>
      </tr>
      <tr>
        <td>2</td>
        <td>Мельникова Дарья Сергеевна</td>
        <td>03.12.1995</td>
        <td>Системный аналитик</td>
        <td>140 000</td>
      </tr>
      <tr>
        <td>3</td>
        <td>Костин Андрей Николаевич</td>
        <td>15.06.1991</td>
        <td>DevOps-инженер</td>
        <td>160 000</td>
      </tr>
      <tr>
        <td>4</td>
        <td>Соколова Алина Игоревна</td>
        <td>28.01.1998</td>
        <td>UI/UX дизайнер</td>
        <td>115 000</td>
      </tr>
      <tr>
        <td>5</td>
        <td>Павлов Илья Владимирович</td>
        <td>09.04.1985</td>
        <td>Менеджер проектов</td>
        <td>130 000</td>
      </tr>
    </tbody>
  </table>
</div>
Показать CSS код
.variant-1 {
  container-type: inline-size;
}

.variant-1 table {
  --border-color: #cad5e2;
  --half-border-color: color-mix(in srgb, var(--border-color), transparent 50%);

  background: white;
  color: #1d293d;
  border-collapse: separate;
  border-spacing: 0;
  overflow: hidden;
  border-radius: 0.5rem;
  border: 1px solid var(--border-color);
  border-color: var(--border-color);
}

.variant-1 th {
  border-bottom-width: 2px;
  border-color: #45556c;
  background-color: #f1f5f9;
  padding: 0.5rem 1rem;
  text-align: left;
  vertical-align: bottom;
}

.variant-1 td {
  padding: 0.5rem 1rem;
  vertical-align: baseline;
}

.variant-1 tr:nth-child(even) {
  background-color: #f8fafc;
}

.variant-1 tr:not(:last-child) td {
  border-bottom: 1px solid var(--half-border-color);
}

.variant-1 :is(th, td):nth-child(3),
.variant-1 :is(th, td):nth-child(5) {
  text-align: right;
}

.variant-1 td:nth-child(5) {
  white-space: nowrap;
}

@container (width < 640px) {
  .variant-1 table {
    font-size: 0.875rem;
  }
}

Вариант 2: Карточки

В мобильной версии строки разворачиваем вертикально. Но для каждого значения рядом ставим легенду – значение столбца, которое мы задаём через дата-атрибут или стилями.

Плюсы:

  • Не меняем разметку (значение создаётся через псевдоэлемент before)
  • Весь контент виден сразу без скролла.

Минусы:

  • Трудно сравнивать значения разных строк между собой.

Карточки с data-атрибутами

Для этого сохраним в data-label значение легенды столбца у всех ячеек.

<tr>
  <td data-label="№">1</td>
  <td data-label="ФИО">Белов Максим Артемович</td>
  <td data-label="Дата рождения">22.10.1988</td>
  <td data-label="Должность">Технический директор</td>
  <td data-label="Оклад, руб.">185 000</td>
</tr>

А в CSS установим для псевдоэлемента значение, которое будет автоматически подхватываться из дата-атрибута.

td::before {
  content: attr(data-label) ':';
}
ФИО Дата рождения Должность Оклад, руб.
1 Белов Максим Артемович 22.10.1988 Технический директор 185 000
2 Мельникова Дарья Сергеевна 03.12.1995 Системный аналитик 140 000
3 Костин Андрей Николаевич 15.06.1991 DevOps-инженер 160 000
4 Соколова Алина Игоревна 28.01.1998 UI/UX дизайнер 115 000
5 Павлов Илья Владимирович 09.04.1985 Менеджер проектов 130 000
Показать HTML код
<div class="table-container">
  <table class="variant-2">
    <thead>
      <tr>
        <th width="5%">№</th>
        <th width="40%">ФИО</th>
        <th width="15%">Дата рождения</th>
        <th width="20%">Должность</th>
        <th width="20%">Оклад, руб.</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td data-label="№">1</td>
        <td data-label="ФИО">Белов Максим Артемович</td>
        <td data-label="Дата рождения">22.10.1988</td>
        <td data-label="Должность">Технический директор</td>
        <td data-label="Оклад, руб.">185 000</td>
      </tr>
      <tr>
        <td data-label="№">2</td>
        <td data-label="ФИО">Мельникова Дарья Сергеевна</td>
        <td data-label="Дата рождения">03.12.1995</td>
        <td data-label="Должность">Системный аналитик</td>
        <td data-label="Оклад, руб.">140 000</td>
      </tr>
      <tr>
        <td data-label="№">3</td>
        <td data-label="ФИО">Костин Андрей Николаевич</td>
        <td data-label="Дата рождения">15.06.1991</td>
        <td data-label="Должность">DevOps-инженер</td>
        <td data-label="Оклад, руб.">160 000</td>
      </tr>
      <tr>
        <td data-label="№">4</td>
        <td data-label="ФИО">Соколова Алина Игоревна</td>
        <td data-label="Дата рождения">28.01.1998</td>
        <td data-label="Должность">UI/UX дизайнер</td>
        <td data-label="Оклад, руб.">115 000</td>
      </tr>
      <tr>
        <td data-label="№">5</td>
        <td data-label="ФИО">Павлов Илья Владимирович</td>
        <td data-label="Дата рождения">09.04.1985</td>
        <td data-label="Должность">Менеджер проектов</td>
        <td data-label="Оклад, руб.">130 000</td>
      </tr>
    </tbody>
  </table>
</div>
Показать CSS код
.table-container {
  container-type: inline-size;
}

.variant-2 {
  --border-color: #cad5e2;
  --half-border-color: color-mix(in srgb, var(--border-color), transparent 50%);

  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  overflow: hidden;
  border-radius: 0.5rem;
  border: 1px solid var(--border-color);
  border-color: var(--border-color);
}

.variant-2 tr:nth-child(even) {
  background-color: #f8fafc;
}

.variant-2 th {
  border-bottom-width: 2px;
  border-color: #45556c;
  background-color: #f1f5f9;
  padding: 0.5rem 1rem;
  text-align: left;
  vertical-align: bottom;
}

.variant-2 td {
  padding: 0.5rem;
  vertical-align: baseline;
}

.variant-2 td:nth-child(5) {
  white-space: nowrap;
}

@container (width < 640px) {
  .variant-2 {
    font-size: 0.875rem;
  }

  .variant-2 thead {
    display: none;
  }

  .variant-2 td {
    display: grid;
    grid-template-columns: 15ch 1fr;
    gap: 0.5rem;
  }

  .variant-2 td::before {
    content: attr(data-label) ':';
    font-weight: bold;
  }

  .variant-2 td:first-child {
    padding-top: 1.25rem;
  }

  .variant-2 td:last-child {
    padding-bottom: 1.25rem;
  }

  .variant-2 tr:not(:last-child) td:last-child {
    border-bottom: 1px solid var(--half-border-color);
  }
}

@container (width >= 640px) {
  .variant-2 td {
    padding-inline: 1rem;
  }

  .variant-2 tr:not(:last-child) td {
    border-bottom: 1px solid var(--half-border-color);
  }

  .variant-2 :is(th, td):nth-child(3),
  .variant-2 :is(th, td):nth-child(5) {
    text-align: right;
  }
}

Карточки с статическим CSS

Другой вариант – это вручную прописывать значения для каждой колонки в CSS. В этом случае оставляем HTML чистым.

Здесь зависит только от того, как проще для вас. Этот вариант немного экономнее в плане размера HTML-файла. Также, если вручную добавляете строки таблицы, а не через CRM, то лучше выбрать такой вариант – так меньше шанс ошибиться, и легче переделывать.

td:nth-child(1)::before {
  content: '№:';
}
td:nth-child(2)::before {
  content: 'ФИО:';
}
td:nth-child(3)::before {
  content: 'Дата рождения:';
}
td:nth-child(4)::before {
  content: 'Должность:';
}
td:nth-child(5)::before {
  content: 'Оклад, руб.:';
}
ФИО Дата рождения Должность Оклад, руб.
1 Белов Максим Артемович 22.10.1988 Технический директор 185 000
2 Мельникова Дарья Сергеевна 03.12.1995 Системный аналитик 140 000
3 Костин Андрей Николаевич 15.06.1991 DevOps-инженер 160 000
4 Соколова Алина Игоревна 28.01.1998 UI/UX дизайнер 115 000
5 Павлов Илья Владимирович 09.04.1985 Менеджер проектов 130 000
Показать HTML код
<div class="table-container">
  <table class="variant-3">
    <thead>
      <tr>
        <th width="5%">№</th>
        <th width="40%">ФИО</th>
        <th width="15%">Дата рождения</th>
        <th width="20%">Должность</th>
        <th width="20%">Оклад, руб.</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>1</td>
        <td>Белов Максим Артемович</td>
        <td>22.10.1988</td>
        <td>Технический директор</td>
        <td>185 000</td>
      </tr>
      <tr>
        <td>2</td>
        <td>Мельникова Дарья Сергеевна</td>
        <td>03.12.1995</td>
        <td>Системный аналитик</td>
        <td>140 000</td>
      </tr>
      <tr>
        <td>3</td>
        <td>Костин Андрей Николаевич</td>
        <td>15.06.1991</td>
        <td>DevOps-инженер</td>
        <td>160 000</td>
      </tr>
      <tr>
        <td>4</td>
        <td>Соколова Алина Игоревна</td>
        <td>28.01.1998</td>
        <td>UI/UX дизайнер</td>
        <td>115 000</td>
      </tr>
      <tr>
        <td>5</td>
        <td>Павлов Илья Владимирович</td>
        <td>09.04.1985</td>
        <td>Менеджер проектов</td>
        <td>130 000</td>
      </tr>
    </tbody>
  </table>
</div>
Показать CSS код
.table-container {
  container-type: inline-size;
}

.variant-3 {
  --border-color: #cad5e2;
  --half-border-color: color-mix(in srgb, var(--border-color), transparent 50%);

  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  overflow: hidden;
  border-radius: 0.5rem;
  border: 1px solid var(--border-color);
  background-color: white;
}

.variant-3 tr:nth-child(even) {
  background-color: #f8fafc;
}

.variant-3 th {
  border-bottom-width: 2px;
  border-color: #45556c;
  background-color: #f1f5f9;
  padding: 0.5rem 1rem;
  text-align: left;
  vertical-align: bottom;
}

.variant-3 td {
  padding: 0.5rem;
  vertical-align: baseline;
}

.variant-3 td:nth-child(5) {
  white-space: nowrap;
}

.variant-3 td::before {
  font-weight: bold;
}

.variant-3 td:nth-child(1)::before {
  content: '№:';
}
.variant-3 td:nth-child(2)::before {
  content: 'ФИО:';
}
.variant-3 td:nth-child(3)::before {
  content: 'Дата рождения:';
}
.variant-3 td:nth-child(4)::before {
  content: 'Должность:';
}
.variant-3 td:nth-child(5)::before {
  content: 'Оклад, руб.:';
}

@container (width < 640px) {
  .variant-3 {
    font-size: 0.875rem;
  }

  .variant-3 thead {
    display: none;
  }

  .variant-3 td {
    display: grid;
    grid-template-columns: 15ch 1fr;
    gap: 0.5rem;
  }

  .variant-3 td:first-child {
    padding-top: 1.25rem;
  }

  .variant-3 td:last-child {
    padding-bottom: 1.25rem;
  }

  .variant-3 tr:not(:last-child) td:last-child {
    border-bottom: 1px solid var(--half-border-color);
  }
}

@container (width >= 640px) {
  .variant-3 td {
    padding-inline: 1rem;
  }

  .variant-3 td::before {
    display: none;
  }

  .variant-3 tr:not(:last-child) td {
    border-bottom: 1px solid var(--half-border-color);
  }

  .variant-3 :is(th, td):nth-child(3),
  .variant-3 :is(th, td):nth-child(5) {
    text-align: right;
  }
}

Отдельные карточки

Дальнейшее развитие прошлого варианта. Отличается только дизайном.

ФИО Дата рождения Должность Оклад, руб.
1 Белов Максим Артемович 22.10.1988 Технический директор 185 000
2 Мельникова Дарья Сергеевна 03.12.1995 Системный аналитик 140 000
3 Костин Андрей Николаевич 15.06.1991 DevOps-инженер 160 000
4 Соколова Алина Игоревна 28.01.1998 UI/UX дизайнер 115 000
5 Павлов Илья Владимирович 09.04.1985 Менеджер проектов 130 000
Показать HTML код
<div class="table-container">
  <table class="variant-4">
    <thead>
      <tr>
        <th width="5%">№</th>
        <th width="40%">ФИО</th>
        <th width="15%">Дата рождения</th>
        <th width="20%">Должность</th>
        <th width="20%">Оклад, руб.</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>1</td>
        <td>Белов Максим Артемович</td>
        <td>22.10.1988</td>
        <td>Технический директор</td>
        <td>185 000</td>
      </tr>
      <tr>
        <td>2</td>
        <td>Мельникова Дарья Сергеевна</td>
        <td>03.12.1995</td>
        <td>Системный аналитик</td>
        <td>140 000</td>
      </tr>
      <tr>
        <td>3</td>
        <td>Костин Андрей Николаевич</td>
        <td>15.06.1991</td>
        <td>DevOps-инженер</td>
        <td>160 000</td>
      </tr>
      <tr>
        <td>4</td>
        <td>Соколова Алина Игоревна</td>
        <td>28.01.1998</td>
        <td>UI/UX дизайнер</td>
        <td>115 000</td>
      </tr>
      <tr>
        <td>5</td>
        <td>Павлов Илья Владимирович</td>
        <td>09.04.1985</td>
        <td>Менеджер проектов</td>
        <td>130 000</td>
      </tr>
    </tbody>
  </table>
</div>
Показать CSS код
.table-container {
  container-type: inline-size;
}

.variant-4 {
  --border-color: #cad5e2;
  --half-border-color: color-mix(in srgb, var(--border-color), transparent 50%);

  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  overflow: hidden;
  border-radius: 0.5rem;
  border: 1px solid var(--border-color);
}

.variant-4 th {
  border-bottom-width: 2px;
  border-color: #45556c;
  background-color: #f1f5f9;
  padding: 0.5rem 1rem;
  text-align: left;
  vertical-align: bottom;
}

.variant-4 td {
  padding: 0.5rem 1rem;
  vertical-align: baseline;
}

.variant-4 td:nth-child(5) {
  white-space: nowrap;
}

@container (width < 640px) {
  .variant-4 {
    font-size: 0.875rem;
    border: none;
  }

  .variant-4 thead {
    display: none;
  }

  .variant-4 tbody {
    display: grid;
    gap: 1rem;
  }

  .variant-4 tr {
    border: 1px solid var(--border-color);
    border-radius: 0.5rem;
    overflow: hidden;
  }

  .variant-4 tr:nth-child(even) {
    background-color: white;
  }

  .variant-4 td {
    display: grid;
    grid-template-columns: 15ch 1fr;
    gap: 0.5rem;
    text-align: right;
    border-bottom: 1px solid var(--half-border-color);
  }

  .variant-4 td:last-child {
    border-bottom: none;
  }

  .variant-4 td::before {
    text-align: left;
    font-weight: bold;
  }

  .variant-4 td:nth-child(1)::before {
    content: '№:';
  }
  .variant-4 td:nth-child(2)::before {
    content: 'ФИО:';
  }
  .variant-4 td:nth-child(3)::before {
    content: 'Дата рождения:';
  }
  .variant-4 td:nth-child(4)::before {
    content: 'Должность:';
  }
  .variant-4 td:nth-child(5)::before {
    content: 'Оклад, руб.:';
  }
}

@container (width >= 640px) {
  .variant-4 tr:nth-child(even) {
    background-color: #f8fafc;
  }

  .variant-4 tr:not(:last-child) td {
    border-bottom: 1px solid var(--half-border-color);
  }

  .variant-4 th:nth-child(3),
  .variant-4 th:nth-child(5),
  .variant-4 td:nth-child(3),
  .variant-4 td:nth-child(5) {
    text-align: right;
  }
}

Итоги: какой вариант выбрать?

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

Стратегия 1: Сохранение табличного вида (scroll)

Этот вариант остается единственным, если пользователю необходимо сравнивать значения в разных строках.

  • Когда выбирать: Таблицы с тарифами, технические спецификации, финансовые отчеты.
  • Главный плюс: Глаз легко перемещается вертикально по столбцу, сравнивая показатели нескольких объектов.

Стратегия 2: Трансформация в карточки (Data-label и CSS)

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

  • Когда выбирать: Списки пользователей, реестры документов, простые каталоги.
  • Главный плюс: Весь объем информации о конкретном объекте (строке) виден сразу, без горизонтального скролла.

Похожие статьи