12k
All articles

Как использовать нативные CSS-миксины вместо Sass

Узнайте, как переносить миксины Sass в нативный CSS с помощью @mixin и @apply, задавать параметры и определить функции, для которых всё ещё нужен Sass.

OpenReplay Team
OpenReplay Team
Как использовать нативные CSS-миксины вместо Sass

Нативный CSS-миксин — это именованный блок объявлений. Он определяется с помощью @mixin --name и вставляется в правило стилей через @apply --name. По сути, это браузерный аналог директив @mixin и @include из Sass.

Если в ваших таблицах стилей уже используются пользовательские свойства (custom properties) и нативная вложенность, то несколько общих миксинов в частичном файле _mixins.scss могут оказаться единственной причиной, по которой вы всё ещё запускаете Sass. В этой статье мы перенесём один реальный миксин на нативный CSS, сравним обе версии и перечислим, какие виды миксинов перенести не получится.

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

  • Нативные CSS-миксины определяются через @mixin --name и применяются через @apply --name. Имена должны быть идентификаторами с двумя дефисами (dashed ident), а @apply выполняет ту же роль, что @include в Sass.
  • Sass при компиляции копирует объявления миксина в каждое правило, которое его подключает. Нативный миксин разворачивает браузер, поэтому в итоговой таблице стилей остаётся одно определение.
  • По данным MDN, ни один браузер не поддерживает CSS-миксины по умолчанию. Они не входят в Baseline, поэтому для продакшена по-прежнему стоит выбирать Sass.
  • У миксинов, которые опираются на карты (maps) Sass, циклы @each или @for, ветвление @if/@else или вычисления на этапе сборки, нативного эквивалента нет.

Sass-миксин, который у вас уже есть

Миксин cover прижимает элемент ко всем четырём краям его ближайшего позиционированного предка. Это один из самых простых миксинов, поэтому он хорошо подходит для проверки. Здесь он используется дважды: для подложки (backdrop) и для оверлея на псевдоэлементе. (Подробнее о стороне Sass читайте в руководстве по миксинам SCSS.)

@mixin cover {
  position: absolute;
  inset: 0;
}

.dialog-backdrop {
  @include cover;
  background: rgb(0 0 0 / 0.6);
}

.media-card.is-loading::after {
  @include cover;
  content: "";
  background: rgb(255 255 255 / 0.7);
}

Само по себе определение миксина не генерирует никакого CSS. Каждый @include копирует объявления в вызывающее правило:

.dialog-backdrop {
  position: absolute;
  inset: 0;
  background: rgb(0 0 0 / 0.6);
}

.media-card.is-loading::after {
  position: absolute;
  inset: 0;
  content: "";
  background: rgb(255 255 255 / 0.7);
}

Чем больше правил подключают миксин, тем больше копий этих двух объявлений попадает в итоговый файл.

Переписываем на нативный CSS-миксин: сравнение

Перенос Sass-миксина, содержащего только объявления, на нативный CSS сводится к трём механическим шагам: добавить два дефиса в начало имени, заменить параметры с $ на параметры с -- и заменить @include на @apply.

Sass

@mixin cover {
  position: absolute;
  inset: 0;
}

.dialog-backdrop {
  @include cover;
  background: rgb(0 0 0 / 0.6);
}

Нативный CSS

@mixin --cover {
  position: absolute;
  inset: 0;
}

.dialog-backdrop {
  @apply --cover;
  background: rgb(0 0 0 / 0.6);
}

@apply заменяет @include, а имя миксина должно быть идентификатором с двумя дефисами. Поскольку развёртывание выполняет браузер, в поставляемой таблице стилей содержится единственное определение --cover. Тело миксина состоит только из объявлений, без обёртки @result: рабочая группа CSSWG приняла решение отказаться от @result в миксинах. В самом черновике спецификации указано, что миксины проработаны гораздо хуже, чем пользовательские функции, поэтому синтаксис ещё может измениться.

Поддержка CSS-миксинов в браузерах

Использовать нативные CSS-миксины в продакшене пока нельзя. Согласно руководству MDN по пользовательским функциям и миксинам, ни один браузер не поддерживает миксины по умолчанию, а значит, функциональность не входит в Baseline. Дальше всех продвинулся Chromium: команда опубликовала Intent to Prototype для CSS-миксинов, и эта работа носит экспериментальный характер.

В качестве прогрессивного улучшения нативные CSS-миксины тоже не подходят. Согласно правилам обработки ошибок в CSS, парсер отбрасывает конструкции, которые не понимает. Браузер без поддержки пропустит @apply, и элемент, получающий position и inset только из миксина, останется без обоих свойств. Для продакшена сохраняйте версию на Sass, а с нативными миксинами экспериментируйте в обычном файле .css, который компилятор Sass не обрабатывает.

Перенос параметров миксина

Параметры Sass-миксинов, через которые передаются простые значения, без проблем переносятся в нативные CSS-миксины. Миксины, которые вычисляют значения, содержат ветвления или циклы на этапе сборки, так не переносятся. Вот cover с аргументом для отступа:

// Sass
@mixin cover($inset: 0) {
  position: absolute;
  inset: $inset;
}
.frame::before { @include cover(0.5rem); content: ""; }
/* Native */
@mixin --cover(--inset) {
  position: absolute;
  inset: var(--inset);
}
.frame::before { @apply --cover(0.5rem); content: ""; }

Каждый параметр становится приватным свойством внутри тела миксина, а его значение читается через var(). Другие стили на странице его не видят. Резервные значения var() работают в теле миксина так же, как в любом другом объявлении. В черновике спецификации CSS Custom Functions and Mixins также описан собственный синтаксис для типизации параметров и задания значений по умолчанию. Актуальную форму уточняйте в черновике и не рассчитывайте, что резервное значение ведёт себя в точности как значение по умолчанию.

В первом публичном рабочем черновике (First Public Working Draft) от 15 мая 2025 года скобки требовались даже у миксина без параметров, а более поздние версии позволяют их опускать. Старые прототипные сборки требовали записи @mixin --a() и @apply --a();, поэтому вам могут встретиться обе формы.

У следующих возможностей Sass-миксинов нет нативного эквивалента в CSS:

  • циклы @each и @for;
  • ветвление @if/@else и @error;
  • карты и map.get из модуля sass:map;
  • вычисления на этапе сборки, например math.div или преобразование единиц.

Миксин для брейкпоинтов наглядно показывает, где проходит граница:

@use "sass:map";

$breakpoints: (sm: 40rem, md: 64rem);

@mixin bp($name) {
  @if not map.has-key($breakpoints, $name) {
    @error "Unknown breakpoint: #{$name}";
  }
  @media (min-width: map.get($breakpoints, $name)) {
    @content;
  }
}

Спецификация определяет @contents как аналог @content из Sass, так что в теории нативный миксин может принять блок от вызывающего кода и обернуть его в @media. Не переносятся поиск по карте, ветка с @error и переменная ширина внутри медиазапроса.

Что дают нативные CSS-миксины?

По сравнению с Sass-миксинами у нативных CSS-миксинов два преимущества: не нужен этап сборки, а аргументы вычисляются в браузере, а не при компиляции. Первое преимущество простое: браузер читает @mixin и @apply напрямую, поэтому CSS, который вы пишете, и есть CSS, который вы поставляете.

Второе преимущество менее очевидно. Sass тоже умеет выводить ссылки var(), поэтому Sass-миксин может генерировать объявления, реагирующие на пользовательские свойства во время выполнения. Но изменить аргумент после компиляции Sass не может: cover(0.5rem) один раз, во время сборки, превращается в inset: 0.5rem. Нативные миксины обрабатываются в браузере, и спецификация предусматривает аргументы, значения которых зависят от элемента, к которому применяется миксин. Однако это поведение описано только в спецификации и не реализовано ни в одном выпущенном движке.

Для сравнения: пользовательские функции — родственная возможность из того же модуля, определяемая через `@function», — возвращают одно значение, а миксины возвращают блок объявлений.

Когда стоит оставить Sass?

Миксины, содержащие только объявления (cover, центрирование, visually-hidden), переносятся без проблем. Миксины, которые генерируют селекторы, перебирают списки или вычисляют значения на этапе сборки, не переносятся.

МиксинПереносится без проблем?Что меняетсяПочему нет
Cover / insetДаимя с --, @apply, var(--inset)—
Visually-hiddenДаимя с --, @apply—
ЦентрированиеДаимя с --, @apply—
БрейкпоинтыНетТеоретически — оборачивание блока через @contentsПоиск по карте, @error, переменная ширина в медиазапросе

Продолжайте использовать @mixin и @include из Sass для циклов, генерирующих утилитарные классы, для токенов на основе карт и для хелперов брейкпоинтов. Для всего продакшен-кода используйте Sass, пока браузеры не начнут поддерживать миксины по умолчанию.

Заключение

Перевести на нативный синтаксис миксин вроде cover, содержащий только объявления, можно быстро и почти механически. Момент перехода определяется поддержкой в браузерах, а её пока нет. Практический следующий шаг — разделить миксины на две группы: содержащие только объявления и опирающиеся на логику Sass. Перепишите первую группу в отдельном файле .css и проверьте её в экспериментальной сборке Chromium. Sass-версии оставьте на месте, пока MDN не сообщит о поддержке по умолчанию.

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

Как протестировать нативные CSS-миксины в Chrome?

Прототип миксинов в Chromium скрыт за флагом функции CSSMixins. Чтобы его опробовать, запустите Chrome Canary из командной строки с аргументом --enable-features=CSSMixins. Реализация неполная и меняется по мере того, как CSSWG решает вопросы спецификации, поэтому некоторые примеры могут не работать или вести себя иначе, чем описано в черновике. Используйте её только для экспериментов и обратной связи по спецификации, но никогда для продакшен-стилей.

Почему нельзя заменить переменную брейкпоинта из Sass на var() в медиазапросе?

Функция var() работает только внутри значений свойств, а условия медиазапросов значениями свойств не являются. Пользовательские свойства вычисляются для каждого элемента через каскад, тогда как медиазапрос вычисляется относительно области просмотра или устройства, а не какого-либо элемента. Поэтому условие вида min-width: var(--md) недопустимо, и ширину брейкпоинта, хранящуюся в переменной, нельзя перенести из Sass в нативный медиазапрос.

Может ли нативный CSS-миксин содержать вложенные селекторы или медиазапросы?

Да, согласно спецификации. Черновик CSS Custom Functions and Mixins допускает внутри тела миксина вложенные правила стилей, например блок с амперсандом и ::after, а также условные правила вроде @media, аналогично тому, как их может генерировать Sass-миксин. Однако ни один браузер не поддерживает миксины по умолчанию, поэтому речь идёт о поведении, заложенном в спецификацию, а не о том, что работает в стабильном движке.

postcss-mixins — это то же самое, что нативные CSS-миксины?

Нет. Плагин postcss-mixins определяет миксин через @define-mixin, вызывает его через @mixin и использует параметры со знаком доллара, которые разворачиваются на этапе сборки. В нативном CSS миксин определяется через @mixin, вызывается через @apply, а развёртывает его браузер. Ключевое слово @mixin означает в этих двух системах противоположные вещи, поэтому при переходе с postcss-mixins на нативный синтаксис придётся переписать и определения, и места вызова.

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.