Урок 11. Теория: mobile-first и контейнерные запросы

📁 Раздел: Раскладка ⏱️ Время изучения: ~50 мин 🎯 Сложность: Средняя

⚡ Кратко

Приставка — это медиазапрос «от указанной ширины и выше». Поэтому базовые классы описывают мобильный вид, а приставки постепенно усложняют раскладку. Диапазон задаётся связкой: md:max-lg:hidden — только между 768 и 1024. Container queries (@container + @md:) привязывают правила к ширине родителя, а не окна.

Mobile-first: почему база — это мобильный

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

<!-- одна колонка везде; от 640px — две; от 1024px — четыре -->
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">…</div>
Почему так, а не наоборот: мобильная версия — самая ограниченная. Начав с неё, вы вынуждены оставить только необходимое; начав с десктопа, придётся «отрезать» лишнее, и что-нибудь обязательно останется торчать.

Mobile-first — как собирать рюкзак в поход: сначала кладёшь то, без чего нельзя, потом, если место осталось, добавляешь удобства. Desktop-first — это набить чемодан дома и выбрасывать вещи по дороге: выбрасывать всегда жальче, и в итоге тащишь лишнее.

Шкала брейкпоинтов

ПриставкаШирина отОбычно это
— (база)0телефон
sm:40rem / 640pxкрупный телефон, узкое окно
md:48rem / 768pxпланшет
lg:64rem / 1024pxноутбук
xl:80rem / 1280pxдесктоп
2xl:96rem / 1536pxбольшой монитор

Живой пример — растяните окно браузера и следите за подписью:

Текущий диапазон:

база — до 640px

<div class="rounded-lg border border-slate-200 bg-white p-4 text-sm">
  <p class="text-slate-700">Текущий диапазон:</p>
  <p class="mt-1 font-semibold text-rose-600 sm:hidden">база — до 640px</p>
  <p class="mt-1 hidden font-semibold text-amber-600 sm:block md:hidden">sm — 640–768px</p>
  <p class="mt-1 hidden font-semibold text-emerald-600 md:block lg:hidden">md — 768–1024px</p>
  <p class="mt-1 hidden font-semibold text-sky-600 lg:block xl:hidden">lg — 1024–1280px</p>
  <p class="mt-1 hidden font-semibold text-indigo-600 xl:block">xl и выше — от 1280px</p>
</div>

Диапазоны и «только до»

Иногда правило нужно ограничить сверху. Для этого есть приставка max-* и их сочетания:

ЗаписьСмысл
md:flexот 768px и выше
max-md:hiddenдо 768px (не включая)
sm:max-lg:grid-cols-2от 640 до 1024
min-[900px]:blockот произвольной ширины
max-[420px]:text-xsдо произвольной ширины

max-sm: подсвечиваюсь только на узком экране

sm:max-lg: подсвечиваюсь только между 640 и 1024

<div class="space-y-2 text-sm">
  <p class="rounded bg-slate-100 px-3 py-2 text-slate-700 max-sm:bg-rose-100 max-sm:text-rose-900">max-sm: подсвечиваюсь только на узком экране</p>
  <p class="rounded bg-slate-100 px-3 py-2 text-slate-700 sm:max-lg:bg-emerald-100 sm:max-lg:text-emerald-900">sm:max-lg: подсвечиваюсь только между 640 и 1024</p>
</div>

Container queries: реакция на родителя

Медиазапрос знает только ширину окна. Но карточка может стоять и в широкой колонке, и в узком сайдбаре — а выглядеть должна по-разному. Именно это решают container queries.

Схема простая: родителю — @container, детям — приставки @sm:, @md:, @lg:, которые смотрят на ширину этого родителя.

<div class="@container">
  <div class="flex flex-col @md:flex-row @md:items-center">
    …
  </div>
</div>

Широкий контейнер

Карточка

В широком контейнере — в строку.

Узкий контейнер

Карточка

В узком — в столбик.

<div class="grid gap-4 lg:grid-cols-[1fr_260px]">
  <div class="@container rounded-lg border border-slate-200 bg-white p-3">
    <p class="mb-2 text-xs text-slate-500">Широкий контейнер</p>
    <div class="flex flex-col gap-3 @md:flex-row @md:items-center">
      <div class="size-12 shrink-0 rounded-lg bg-sky-200"></div>
      <div>
        <p class="text-sm font-semibold text-slate-900">Карточка</p>
        <p class="text-xs text-slate-600">В широком контейнере — в строку.</p>
      </div>
    </div>
  </div>
  <div class="@container rounded-lg border border-slate-200 bg-white p-3">
    <p class="mb-2 text-xs text-slate-500">Узкий контейнер</p>
    <div class="flex flex-col gap-3 @md:flex-row @md:items-center">
      <div class="size-12 shrink-0 rounded-lg bg-emerald-200"></div>
      <div>
        <p class="text-sm font-semibold text-slate-900">Карточка</p>
        <p class="text-xs text-slate-600">В узком — в столбик.</p>
      </div>
    </div>
  </div>
</div>

Обратите внимание: один и тот же компонент с одинаковыми классами выглядит по-разному, потому что стоит в контейнерах разной ширины. С медиазапросами так не получится: они оба «видят» одно и то же окно.

Контейнеры можно именовать: @container/sidebar у родителя и @lg/sidebar:flex у потомка. Это нужно, когда контейнеров несколько и важно указать, на какой именно смотреть.

Свои брейкпоинты

@theme {
  --breakpoint-tablet: 60rem;   /* появится приставка tablet: */
  --breakpoint-2xl: initial;    /* убрать ненужный брейкпоинт */
}

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

Что адаптируют чаще всего

ЧтоМобильныйДесктоп
Сеткаgrid-cols-1lg:grid-cols-3
Направлениеflex-colmd:flex-row
Отступыpx-4 py-6md:px-8 md:py-12
Размер заголовкаtext-2xlmd:text-4xl
Видимостьhiddenmd:block
Ширина сайдбараво всю ширинуlg:w-64