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

Часть 1: Введение

Впервые фронтенд-разработчик понимает, что конструкция типа --variable: value в CSS - это не переменная, когда пытается использовать её в медиавыражении:

:root {
  --tablet-width: 768px;
}

@media (min-width: var(--tablet-width)) {
  .text {
    font-size: 24px;
  }
}

или так:

:root {
  --tablet-width: (min-width: 768px);
}

@media (var(--tablet-width)) {
  .text {
    font-size: 24px;
  }
}

В этот самый момент срочно приходится искать как работают кастомные свойства и выяснять почему они работают не так как ожидается, или вообще не работают. Во второй части статьи я объясню этот момент.

С тех самых пор как появились кастомные свойства, W3C начали думать, как сделать так, чтобы кастомки заработали в медиавыражениях. Короткий ответ: никак! Нужно изобретать новую директиву — @custom-media.

Обычные кастомные свойства не умеют подставляться внутрь @media, потому что там требуется логическая структура, а не текстовая замена. Поэтому в спецификации появилась совершенно отдельная штука — правило @custom-media, которое позволяет объявлять именованные алиасы для медиавыражений. Это не подстановка текста, а именно логическое вычисление.

Все наработки по использованию кастомок в медиавыражениях вылились в спецификацию Media Queries Level 5, а уже 9 декабря 2025 года, за флагом layout.css.custom-media.enabled их можно будет попробовать в 146 версии Firefox.

Ежедневное использование медиавыражений:

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

.text {
  font-size: 20px;
}

@media (min-width: 768px) {
  .text {
    font-size: 24px;
  }
}

Кто-то уже позволяет себе в проде использовать медиафичу range:

.text {
  font-size: 20px;
}

/* аналогично (min-width: 768px) */
@media (width >= 768px) { 
  .text {
    font-size: 24px;
  }
}

А кто-то даже CSS Nesting использует:

.text {
  font-size: 20px;

  /* директива внутри селектора */
  @media (width >= 768px) {
    font-size: 24px;
  }
}

Ну и давайте посмотрим на использование переменных в медиавыражениях в препроцессорах, чтобы быстрее понять кастомные медиавыражения:

$tablet-width: 768px;

.text {
  font-size: 20px;

  // в препроцессоре переменную можно использовать как значение
  // даже в медиавыражениях
  @media (min-width: $tablet-width) { 
    font-size: 24px;
  }
}

Проблема

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

$mobile-width: 320px;
$tablet-width: 768px;
$desktop-width: 1024px;

Использование переменных для брейкпоинтов для фронтенд-разработчика - это уже что-то очень интуитивно понятное. Фронтендер уже не обращает внимание на значения, он смотрит и по памяти использует название переменной.

@media (min-width: $desktop-width){}

сразу же говорит для какого экрана будут изменения. Такого же процесса работы в CSS можно добиться с помощью кастоных медиавыражений.

Кастомные медиавыражения

Кастомные медиавыражения включают в себя лучшее из двух миров: переменных в препроцессарах и кастомных свойств в CSS.

Создадим первое кастомное медиавыражение:

@custom-media --mobile-width (min-width: 320px);

Синтаксис

Синтаксис кастомного медиавыражения:

@custom-media <extension-name> [ <media-query-list> | true | false ];

1) @custom-media - это ключевое слово — директива, которая объявляет новое кастомное медиавыражение (alias). По смыслу похоже на @font-face, @property, @media, но здесь вы создаёте новое имя, которое потом сможете использовать в медиавыражениях.

2) <extension-name> - имя кастомного медиавыражения.

Как выглядит:

  • должно начинаться с --;

  • может содержать буквы, цифры, дефисы.

Примеры корректных имён:

--narrow-window
--modern
--supports-pen
--retina-only

Эти имена затем используются в @media:

@media (--narrow-window) {}

3) [ <media-query-list> | true | false ] - Это значение alias-а. Оно может быть одним из трёх вариантов:

- <media-query-list> - список медиавыражений. Фактически обычный контент, который стоит после @media.

- true - Это объявление кастомного медиавыражения, которое всегда истинно.

@custom-media --always-true true;

@media (--always-true) {
  /* Всегда сработает */
}

- false - Это объявление медиавыражения, которое всегда ложно.

Примеры

@custom-media --modern (hover), (color);

Расшифровка:

- @custom-media — объявляем кастомное медиавыражение

- --modern — имя alias-а

- (hover), (color) — логическое выражение: true, если хотя бы одно истинно

Использование:

@media (--modern) and (width > 900px) {
  /* стили для современных устройств */
}

Второй пример

В препроцессоре обычно создаётся отдельный файл для работы с брейкпоинтами:

/* breakpoints.scss */
$mobile: 480px;
$tablet: 768px;
$laptop: 1024px;
$desktop: 1280px;
$wide: 1536px;

в кастомных медиавыражения этот пример будет выглядеть вот так:

@custom-media --mobile (width >= 480px);
@custom-media --tablet (width >= 768px);
@custom-media --laptop (width >= 1024px);
@custom-media --desktop (width >= 1280px);
@custom-media --wide (width >= 1536px);

Использование:

@media (--tablet) {
  .sidebar {
    display: block;
  }
}

Как видите, любой, кто уже поработал с переменными в медиавыражениях в препроцессорах достаточно быстро разберётся с @custom-media в CSS.

Хочу использовать прямо сейчас

Поскольку кастомные медиавыражения довольно просто превратить в обычные медиавыражения достаточно быстро появился плагин для postcss postcss-custom-media, который по сути берёт значение @custom-media и подставляет его вместо <media-query-list>

@custom-media --mobile (min-width >= 480px);

@media (--mobile) {
  .text {
    font-size: 24px;
  }
}

в итоговый CSS генерируется

@media (width >= 480px) {
  .text {
    font-size: 24px;
  }
}

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

Вторая часть для любознательных

Что там с @custom-media и почему нельзя было сразу сделать нормально, чтобы использовать кастомные свойства(--mobile-width) в медиавыражениях?

Чтобы полностью разобраться в процессе того как браузер обрабатывает значения нужно прочесть десятки спецификаций, узнать о всех этапах обработки:

  • фильтрация;

  • каскад;

  • значение по умолчанию;

  • различия вычисленных, используемых и действительных значений;

  • типы и их особенности вычисления

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

В коротком варианте, достаточным для понимания, работа браузера по вычислению значений в старом стиле выглядит так:

  1. Получение CSS-файла

  2. Парсинг CSS

  3. Этап правил. Здесь читаются обычные медиавыражения

  4. Каскад + наследование

  5. Вычисление значений. Вот тут разрешаются var() и кастомные свойства. На этом этапе медиа уже давно обработаны (их невозможно пересчитать через var()).

Коротким ответом на изначальный вопрос станет: медиавыражения вычисляются гораздо раньше, чем кастомные выражения. Именно поэтому медиавыражения не понимают кастомные свойства, так как они ещё не вычислились

:root {
  --tablet-width: 768px;
}

/*CSS: я не понимаю чему равно значение */
@media (min-width: var(--tablet-width)) { 
  .text {
    font-size: 24px;
  }
}

Решением же стало добавление кастомных медиавыражений, которые вычисляются до медиавыражений:

  1. Получение CSS-файла

  2. Парсинг CSS

  3. Сбор @custom-media Создаётся таблица алиасов --mobile -> min-width: 768px

  4. Этап правил. Сначала раскрываются custom-media: @media(--mobile) -> media(min-width: 320px. Затем @media вычисляются: если min-width: 768pxtrue, правило активно, если false → правило игнорируется

  5. Каскад + наследование

  6. Вычисление значений. Вот тут разрешаются var() и кастомные свойства.

На это всё. Спасибо, что прочитали.

Комментарии (0)