Mobile-first на практике: медиазапросы без боли
Разбираем, почему min-width работает предсказуемее max-width, где ломается адаптивная сетка и когда медиазапросы вообще не нужны, если под рукой clamp и container queries.

Вы верстаете карточку товара. На десктопе она в три колонки, на планшете в две, на телефоне в одну. Пишете медиазапрос max-width: 768px, потом ещё один на max-width: 480px, и уже на четвёртом брейкпоинте перестаёте понимать, какой стиль перекрывает какой. Знакомо. Дальше — почему направление медиазапроса меняет всё, где адаптивная сетка ломается и в каких местах медиазапросы сегодня можно вообще не писать.
Материал для тех, кто уже верстает и упёрся в кашу из брейкпоинтов. Код рассчитан на актуальные браузеры 2024–2025 годов: свойства clamp(), CSS Grid и container queries поддерживаются во всех вечнозелёных браузерах. Тему flexbox-раскладки внутри отдельного компонента здесь не разворачиваю — говорим только про адаптивность на уровне макета.
Почему max-width тянет за собой лишний код
Есть два способа задавать медиазапросы: от большого экрана к маленькому (max-width) и от маленького к большому (min-width). На первый взгляд разницы нет — оба покрывают нужные диапазоны. Разница в том, что вы пишете базовыми стилями и что переопределяете.
При подходе desktop-first базой становится сложная многоколоночная раскладка, а каждый медиазапрос её упрощает. Проблема: чтобы «убрать» колонку, вы переопределяете уже заданные свойства. Стилей копится больше, каскад запутывается, и на узком экране браузер сначала применяет тяжёлую раскладку, а потом ломает её.
Mobile-first разворачивает логику. База — самая простая одноколоночная версия. Медиазапросы min-width только добавляют сложность по мере роста экрана. Вы не отменяете стили, а наслаиваете. Каскад читается сверху вниз как история: сначала телефон, потом планшет, потом десктоп.
Mobile-first — это не про «сначала нарисуем мобильную версию». Это про то, что базовый CSS без единого медиазапроса уже даёт рабочую страницу на самом узком экране.
Как это выглядит в коде
Одна и та же сетка карточек, два подхода. Desktop-first:
/* База: три колонки */
.cards {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
}
/* Планшет: убираем до двух */
@media (max-width: 768px) {
.cards {
grid-template-columns: repeat(2, 1fr);
}
}
/* Телефон: убираем до одной */
@media (max-width: 480px) {
.cards {
grid-template-columns: 1fr;
}
}
Mobile-first:
/* База: одна колонка, работает на телефоне без медиазапросов */
.cards {
display: grid;
grid-template-columns: 1fr;
gap: 24px;
}
/* От 480px: две колонки */
@media (min-width: 480px) {
.cards {
grid-template-columns: repeat(2, 1fr);
}
}
/* От 768px: три колонки */
@media (min-width: 768px) {
.cards {
grid-template-columns: repeat(3, 1fr);
}
}
Кода примерно столько же, но во втором варианте каждый следующий блок добавляет, а не отменяет. Когда карточка обрастёт десятком свойств — отступами, тенями, размерами шрифта, — разница станет решающей: в mobile-first вы переопределяете только то, что реально меняется.
Где брать брейкпоинты
Частая ошибка новичков — привязывать брейкпоинты к моделям устройств: «375 для айфона, 768 для айпада». Так делать не стоит: моделей сотни, диагонали плавают, а завтра выйдет новая. Брейкпоинт ставится там, где ломается ваш контент, а не там, где кончается конкретный телефон.
Рабочий приём: тяните окно браузера от узкого к широкому и смотрите, в какой момент раскладка становится некрасивой — текст слишком растянулся, карточки стали уродливо узкими, две колонки просят третью. Вот там и ставьте брейкпоинт.
Ориентировочные диапазоны, от которых можно отталкиваться, но не как догма:
| Диапазон, px | Класс устройств | Что обычно меняется |
|---|---|---|
| до 480 | телефоны | одна колонка, крупные тап-зоны |
| 480–768 | крупные телефоны, узкие планшеты | две колонки, появляется боковой отступ |
| 768–1024 | планшеты | сетка на 2–3 колонки, шире контейнер |
| 1024 и выше | десктоп | полная раскладка, фиксированный max-width контента |
Задавайте брейкпоинты в em или px — оба варианта рабочие. em в медиазапросе считается от базового размера шрифта браузера, что помогает при масштабировании страницы пользователем; px проще читать. Выберите одно и держитесь его во всём проекте.
Медиазапросы, которые можно не писать
Часть адаптивности сегодня решается без единого @media. Это не замена медиазапросам, а способ убрать половину брейкпоинтов, которые раньше приходилось описывать вручную.
clamp для плавных размеров
Раньше размер заголовка меняли ступенями: 24px на телефоне, 32px на планшете, 48px на десктопе — три медиазапроса. clamp() делает это одной строкой, размер растёт плавно вместе с шириной окна:
.title {
/* минимум 24px, идеал 5% ширины окна, максимум 48px */
font-size: clamp(24px, 5vw, 48px);
}
Между 24 и 48 пикселями шрифт масштабируется линейно по 5vw, а на краях упирается в границы. Ни одного медиазапроса. Тот же приём работает для отступов и ширины контейнера.
auto-fit для сеток без брейкпоинтов
Сетка карточек из первого примера меняла число колонок медиазапросами. Grid умеет сам решать, сколько колонок влезет:
.cards {
display: grid;
/* колонки минимум 240px, растягиваются, число — сколько влезет */
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 24px;
}
На узком экране — одна колонка, на широком — сколько поместится по 240 пикселей. Ни одного @media. Обратная сторона: вы теряете точный контроль над числом колонок на каждом диапазоне. Если дизайн требует ровно двух колонок на планшете и трёх на десктопе — возвращайтесь к медиазапросам.
Container queries — адаптивность от размера родителя
Медиазапрос смотрит на ширину окна. Но одна и та же карточка может стоять и в узком сайдбаре, и в широкой основной колонке. По ширине окна вы этого не различите. Container queries1 реагируют на размер контейнера, в котором лежит компонент:
/* объявляем контейнер */
.card-wrapper {
container-type: inline-size;
}
/* стиль карточки зависит от ширины wrapper, а не окна */
@container (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 120px 1fr;
}
}
Карточка сама решает, встать в столбик или в строку, в зависимости от того, сколько места ей дали. Это то, ради чего медиазапросы никогда не годились: компонент стал по-настоящему переносимым между разными местами макета.
Когда что выбирать
- clamp() — плавное изменение одного числового свойства: размер шрифта, отступ, ширина. Когда не нужны ступени, а нужен плавный рост.
- auto-fit / minmax — сетка одинаковых элементов, где число колонок не критично и может подстраиваться само.
- container queries — переносимый компонент, который встречается в разных по ширине местах макета.
- медиазапросы — когда раскладка меняется скачком и вам нужен точный контроль: скрыть блок, перестроить навигацию, задать ровно три колонки.
Что ломается на практике
Типичные грабли, на которые наступают чаще всего:
- Забыли viewport-мету. Без
<meta name="viewport" content="width=device-width, initial-scale=1">в<head>мобильный браузер отрендерит страницу в виртуальные 980px и уменьшит, будто десктоп. Медиазапросыmin-width: 480pxпри этом не сработают — браузер считает, что экран широкий. Это первое, что нужно проверить, если адаптивность «не включается». - Пересечение диапазонов в mobile-first. Если написать
min-width: 768pxиmax-width: 767pxвперемешку, каскад начнёт конфликтовать. Держитесь одного направления — толькоmin-widthдля основной раскладки. - Горизонтальный скролл на телефоне. Обычно виновата фиксированная ширина элемента (
width: 400px) или картинка безmax-width: 100%. Ищите элемент шире вьюпорта через инструменты разработчика. - Тап-зоны меньше пальца. Кнопки и ссылки на телефоне должны быть комфортными для нажатия — ориентируйтесь примерно на 44×44 пикселя как нижнюю границу. Красивая на десктопе иконка 16px на телефоне превращается в промах.
Что сделать дальше
Откройте свой проект и проверьте направление медиазапросов: если преобладает max-width — при следующем рефакторинге разверните базу в mobile-first, начиная с самого простого варианта. Затем пройдитесь по брейкпоинтам, которые привязаны к моделям устройств, и переставьте их туда, где реально ломается контент. Половину ступенчатых медиазапросов на размеры шрифтов и отступов замените на clamp(). А для компонентов, которые кочуют между сайдбаром и основной колонкой, попробуйте container queries — это то место, где они дают самый заметный выигрыш. Тестируйте не в эмуляторе, а на реальном телефоне: тач, шрифты и прокрутка ведут себя иначе.
1 Container queries — CSS-механизм, позволяющий применять стили в зависимости от размера контейнера-родителя, а не окна браузера. Элемент-контейнер объявляется свойством container-type.
Тест и проверочная: Быстрые ответы с объяснением, почему остальные варианты неверны
Спросить за 10 ₽Источники: MDN: Using media queries, MDN: CSS container queries
Частые вопросы
У меня старый проект, поддержка старых браузеров обязательна. Container queries сработают?
clamp() не масштабирует шрифт — что не так?
В em или px задавать брейкпоинты?
Адаптивность вообще не включается на телефоне, всё как на десктопе. В чём причина?
Стоит ли полностью отказаться от медиазапросов в пользу clamp и container queries?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.