12k
All articles

Когда использовать CSS `if()` и контейнерные стилевые запросы

Сравните CSS if() и container style queries: когда применять каждый подход, ловушки синтаксиса, range syntax и поддержку браузеров в 2026.

OpenReplay Team
OpenReplay Team
Когда использовать CSS `if()` и контейнерные стилевые запросы

Используйте функцию CSS if(), когда один элемент должен самостоятельно определять своё значение inline — токены темизации, состояния компонента, точечная корректировка адаптивного свойства. Обращайтесь к контейнерному стилевому запросу (@container style(...)), когда стиль одного родительского элемента должен управлять набором правил для нескольких потомков. Краткая ментальная модель: if() создаёт условное значение, локальное для элемента; контейнерный стилевой запрос устанавливает контекст, применяя условные блоки правил от родителя к его дочерним элементам.

Оба механизма являются условными конструкциями CSS, оба читают пользовательские свойства, оба используют один и тот же примитив style(), а их возможности пересекаются настолько, что неправильный выбор приводит к неудобному коду. Эта статья разрешает это пересечение: что делает каждый механизм, механическое различие, определяющее выбор, два синтаксических подводных камня, на которые натыкаются разработчики (точки с запятой и нотация с двоеточием против знака равенства), синтаксис диапазонов, теперь доступный в обоих механизмах, таблица принятия решений по сценариям, а также способы поставки кода с учётом того, что поддержка браузерами разделена на три группы по состоянию на середину 2026 года.

Ключевые выводы

  • if() читает пользовательские свойства, объявленные в том же правиле для того же элемента, и возвращает одно значение; @container style() всегда смотрит вверх по каскаду и применяет целые блоки правил к потомкам.
  • Внутри if() пары условие–значение разделяются точками с запятой (не запятыми), а else задаёт запасное значение; если ни одно условие не совпадает и else отсутствует, функция возвращает гарантированно недопустимое значение, которое разрешается в начальное значение свойства.
  • Внутри style() нотация с двоеточием (style(--n: 3)) выполняет сопоставление токенов/строк без каких-либо вычислений, тогда как нотация с оператором сравнения (style(--n = 3)) разбирает обе стороны как числовой тип, предварительно вычисляя calc().
  • По состоянию на середину 2026 года ситуация с поддержкой различается: базовые контейнерные стилевые запросы реализованы во всех четырёх основных движках (Chrome 111+, Edge 111+, Safari 18+, Firefox 151+), тогда как if() и синтаксис диапазонов, включённый по умолчанию, доступны только в Chromium.
  • Поскольку if() работает только в Chromium, каждое объявление с if() требует предшествующего запасного объявления того же свойства или обёртки в @supports.

Что представляет собой каждый механизм в минимальном виде

Функция if() — это условный оператор на уровне значения, который помещается внутрь любого свойства. Её аргументом является разделённый точками с запятой список пар условие–значение, где каждое условие отделяется от значения двоеточием, а необязательный else задаёт значение по умолчанию:

.badge {
  color: if(
    style(--variant: danger): white;
    style(--variant: muted): #555;
    else: black;
  );
}

Каждое условие относится к одному из трёх типов запросов:

  • style() проверяет пользовательское свойство на текущем элементе;
  • media() выполняет встроенный медиазапрос;
  • supports() выполняет запрос возможностей.

Справочник MDN документирует все три типа с одинаковой грамматикой на основе точек с запятой — например, flex-direction: if(media(orientation: landscape): row; else: column;) переключает одно свойство в зависимости от результата медиазапроса.

Контейнерный стилевой запрос — это at-правило @container, условие которого состоит из одной или нескольких нотаций style(), читающих пользовательские свойства содержащего элемента. Он оборачивает блок правил, а не возвращает единственное значение:

.card { --status: active; }

@container style(--status: active) {
  .card-title { font-weight: 700; }
  .card-icon { opacity: 1; }
  .card-meta { color: green; }
}

Согласно руководству MDN по контейнерным запросам, каждый элемент по умолчанию является стилевым контейнером — в отличие от запросов по размеру, для стилевых запросов не требуется устанавливать container-type. Это устраняет основную сложность настройки, которую разработчики ожидают от контейнерных запросов.

Ключевое различие, определяющее выбор

Механическое различие состоит в области поиска. if() читает пользовательские свойства, объявленные в том же правиле для того же элемента, и является самодостаточным, тогда как @container style() всегда смотрит вверх по каскаду к элементу-предку-контейнеру. MDN формулирует это прямо: использование стилевого запроса внутри if() позволяет нацеливаться на элемент в зависимости от того, установлено ли на нём пользовательское свойство, а не проверять стили родителя. Оба механизма явно позиционируются как взаимодополняющие, а не избыточные.

Это различие в области поиска имеет три практических следствия:

  1. Гранулярность вывода. if() устанавливает ровно одно значение. @container применяет целый блок правил — несколько объявлений для нескольких селекторов — когда его условие истинно. Как формулирует MDN, с помощью стилевых запросов if() можно задавать значения только одного свойства, тогда как запросы @container условно применяют целые наборы правил.

  2. Направление. if() может читать свойство, объявленное на том самом элементе, который он стилизует. @container не может читать значение собственного элемента для стилизации этого элемента; он читает значение предка и стилизует потомков.

  3. Область видимости. Только контейнерные стилевые запросы поддерживают область видимости через container-name. Присвойте контейнеру имя, и запрос будет вычисляться только относительно этого контейнера; если именованный контейнер не определяет запрашиваемое свойство, блок просто не выполняется. У if() нет эквивалентного механизма управления областью видимости — он всегда разрешается относительно элемента, на котором находится.

Таким образом, критерий выбора в зоне пересечения таков: если одно свойство на одном элементе должно переключиться, if() создаёт меньше косвенности. Если несколько потомков должны реагировать на одно значение родителя, @container выражает это с помощью единственного условия вместо повторения if() для каждого дочернего элемента.

Синтаксические подводные камни

В if() пары разделяются точками с запятой, а не запятыми. На это натыкаются все, кто привык к JavaScript или к CSS-функциям с запятыми в качестве разделителей, таким как rgb(). Грамматика выглядит так: if(condition-1: value-1; condition-2: value-2; else: fallback), причём точка с запятой после последней пары необязательна. Также между if и открывающей скобкой не должно быть пробела, иначе всё объявление становится недопустимым. Если ни одно условие не совпадает и else опущен, if() возвращает гарантированно недопустимое значение — в контексте обычного свойства это разрешается в начальное значение свойства, что редко является желаемым результатом. Всегда включайте else.

Различие между нотацией с двоеточием и нотацией со знаком равенства внутри style() — тонкая, но важная деталь, которую почти никто не объясняет. Оба механизма — if() и контейнерные стилевые запросы — принимают две нотации с принципиально разным поведением, которое теперь задокументировано на MDN.

Обычная нотация (с двоеточием) выполняет сопоставление токенов/строк вычисленных значений и не производит никаких математических вычислений. Нотация диапазона (с оператором) — использующая =, <, <=, > или >= — разрешает каждую сторону, разбирает обе как числовой тип, вычисляет любой calc() и сравнивает численно. Следствие этого, взятое прямо из примера MDN:

.box { --n: calc(6 / 2); }

/* FALSE: вычисленное значение --n — это строка "calc(6/2)",
   которая не является токеном "3" */
@container style(--n: 3) { /* … */ }

/* TRUE: обе стороны разбираются как <number>, calc(6/2) вычисляется в 3,
   и 3 = 3 */
@container style(--n = 3) { /* … */ }

Таким образом, style(--n: 3) не срабатывает для --n: calc(6/2), тогда как style(--n = 3) срабатывает. Важны ещё два правила. При обычной нотации имя пользовательского свойства должно находиться слева без var()style(var(--n): 3) недопустимо, — тогда как нотация диапазона принимает имя, var(), литерал или calc() с любой стороны в любом порядке. Кроме того, эквивалентные значения (например, blue и #0000ff) совпадают при нотации с двоеточием только в том случае, если свойство зарегистрировано через @property с дескриптором syntax. Вывод: используйте нотацию с двоеточием для сопоставления ключевых слов/строк (style(--theme: dark)), а нотацию с оператором — для числового сравнения (style(--columns >= 3)).

Синтаксис диапазонов теперь доступен в обоих механизмах

Синтаксис диапазонов расширяет оба механизма за пределы точного сопоставления до числового сравнения, принимая операнды из пользовательских свойств, attr() или литеральных значений. Согласно примечаниям к выпуску Chrome 142, сравнение разрешается только тогда, когда обе стороны разрешаются в один и тот же тип данных, ограниченный семью числовыми типами: <length>, <number>, <percentage>, <angle>, <time>, <frequency> и <resolution>. Трёхзначная форма интервала также работает — @container style(0 < --n < 10) — при условии, что оба оператора сравнения указывают в одном направлении.

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

@container style(--count > 99) {
  .badge::after { content: "99+"; }
}

Поскольку нотация диапазона разбирает значения численно, style(--s = new) возвращает false для ключевого слова new, тогда как style(--s: new) возвращает true — ещё одна причина, по которой выбор нотации принципиально важен.

Что использовать в зависимости от сценария

СценарийИнструментПричина
Токен темы → одно свойство (например, color из --theme)if()Один элемент определяет своё значение inline
Состояние компонента переключает несколько стилей дочерних элементов (active/error)@container style()Одно значение родителя управляет блоком правил для потомков
Адаптивная корректировка одного свойстваif() с media()Локальная логика для конкретного свойства; блок @media не нужен
Пороговые значения счётчика значка уведомленияЛюбой, через синтаксис диапазонов@container, если соседние элементы тоже реагируют; if(), если меняется только значок
Текст с учётом контрастности на тематически оформленном родителе@container style()Дочерние элементы читают тему родителя и перестилизуются вместе
Значение с проверкой поддержки функции (например, lch() с запасным вариантом)if() с supports()Замена одного значения в зависимости от поддержки
Многосвойственное изменение макета на контрольной точке@media (ни то, ни другое)Оба инструмента не подходят для масштабных изменений макета

Практическое правило: одно условное значение — задача для if(); условный контекст, перестилизующий поддерево, — задача для контейнерного запроса.

Поставка кода в 2026 году

Поддержка браузерами разделена на три группы, и прежняя формулировка «условный CSS работает только в Chromium» больше не актуальна. Поддержка браузерами по состоянию на июнь 2026 года:

ВозможностьChrome / EdgeSafariFirefox
Контейнерные стилевые запросы (пользовательские свойства, обычный синтаксис)111+18+151+
Функция if()137+Не реализованоНе реализовано
Синтаксис диапазонов в style() / if()142+ (включён по умолчанию)Не реализовано151, за флагом layout.css.attr.enabled

Базовые контейнерные стилевые запросы теперь действительно кроссбраузерны. Они появились в Chrome и Edge 111, достигли WebKit в Safari 18.0 и вошли в Firefox 151, выпущенный 19 мая 2026 года, который добавил поддержку @container style() запросов на основе пользовательских свойств контейнера. Функция if(), напротив, остаётся доступной только в Chromium — MDN помечает её как экспериментальную с ограниченной доступностью, — а синтаксис диапазонов включён по умолчанию только в Chromium. Firefox 151 поставляет синтаксис диапазонов @container style(), но отключённым по умолчанию, за флагом layout.css.attr.enabled.

Практический вывод: if() не деградирует корректно, поэтому сначала пишите объявление по умолчанию и позвольте поддерживающим браузерам его переопределить, либо оборачивайте правило в @supports. Один паттерн охватывает оба механизма:

/* запасной вариант первым — каждый браузер применяет это */
.card-title { font-weight: 400; }

/* прогрессивное улучшение: только движки с поддержкой стилевых запросов применяют это */
@supports (container-type: normal) {
  @container style(--status: active) {
    .card-title { font-weight: 700; }
  }
}

/* if(): сначала статическое значение, затем условное переопределение */
.badge { padding: 0.25em; }
.badge { padding: if(style(--size: lg): 0.5em; else: 0.25em); }

Поскольку if() и синтаксис диапазонов, включённый по умолчанию, в Firefox и Safari отображают запасной вариант — браузеры, которые разработчик, использующий Chrome, редко открывает в процессе разработки, — воспроизведение сессий в разных браузерах является полезной техникой для наблюдения за тем, как запасная ветвь реально отображается в браузере пользователя, а не для предположений о её корректности. Для более глубоких паттернов с calc() и вложенностью внутри if() обзор с акцентом на принятие решений от LogRocket рассматривает темы, которым здесь не хватает места.

Выбирайте по назначению, а не по новизне: условное значение для одного элемента — это if(); условный контекст, перестилизующий поддерево от родителя, — это контейнерный стилевой запрос. Поставляйте любой из них с запасным вариантом уже сегодня, проверяйте таблицу поддержки перед использованием if() в продакшене, и оба механизма всё в большей мере покроют пространство условного CSS, которое раньше требовало хаков с пользовательскими свойствами и JavaScript.

Часто задаваемые вопросы

Могут ли контейнерные стилевые запросы читать пользовательское свойство того же элемента, как это делает if()?

Нет. Контейнерный стилевой запрос всегда смотрит вверх по каскаду к элементу-предку-контейнеру и применяет свои правила к потомкам, поэтому он не может читать пользовательское свойство того самого элемента, который он стилизует. Функция if() работает противоположным образом: она читает пользовательские свойства, объявленные в том же правиле для того же элемента, и разрешает значение inline. Если элемент должен ветвиться на основе собственного свойства, используйте if(); если значение родителя должно перестилизовать его дочерние элементы, используйте контейнерный стилевой запрос.

Требуют ли контейнерные стилевые запросы установки container-type, как это делают запросы по размеру?

Нет. Каждый элемент по умолчанию является стилевым контейнером, поэтому стилевые запросы по пользовательским свойствам работают без объявления container-type. Это отличает их от контейнерных запросов по размеру, которые действительно требуют container-type (например, inline-size) на запрашиваемом предке, прежде чем любое условие по размеру сможет разрешиться. Это задокументировано в руководстве MDN по контейнерным запросам и устраняет основной шаг настройки, который разработчики ожидают при первом обращении к at-правилу @container.

Почему мой стилевой запрос с calc() никогда не совпадает?

Потому что обычная нотация с двоеточием выполняет сопоставление токенов и строк без каких-либо вычислений. Свойство, установленное в calc(6 / 2), вычисляется в строку 'calc(6/2)', которая никогда не совпадает с токеном 3, поэтому style(--n: 3) возвращает false. Для числового сравнения используйте нотацию с оператором сравнения, style(--n = 3), которая разрешает каждую сторону, вычисляет calc(), разбирает обе как числовой тип и сравнивает численно, возвращая true. Используйте форму с двоеточием для сопоставления ключевых слов, а форму с оператором — для числового сравнения.

Корректно ли деградирует if() в Safari и Firefox?

Нет. По состоянию на середину 2026 года if() реализована только в браузерах на Chromium (Chrome и Edge 137+), поэтому Safari и Firefox игнорируют любое объявление, содержащее её, и возвращаются к тому значению, которое применялось ранее. Сначала пишите статическое объявление по умолчанию и позвольте поддерживающим браузерам его переопределить, либо оборачивайте условное правило в @supports. Базовые контейнерные стилевые запросы более безопасны с точки зрения кроссбраузерности: они реализованы в Chrome 111+, Edge 111+, Safari 18+ и Firefox 151+, однако синтаксис диапазонов, включённый по умолчанию, остаётся доступным только в Chromium.

Open-source session replay

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.