12k
All articles

Comment utiliser les mixins CSS natifs à la place de Sass

Découvrez comment porter les mixins Sass en CSS natif avec @mixin et @apply, gérer les paramètres et repérer les fonctions qui exigent encore Sass.

OpenReplay Team
OpenReplay Team
Comment utiliser les mixins CSS natifs à la place de Sass

Un mixin CSS natif est un bloc de déclarations nommé. On le définit avec @mixin --name et on l’insère dans une règle de style avec @apply --name. Il constitue ainsi l’équivalent, côté navigateur, des directives @mixin et @include de Sass.

Si vos feuilles de style utilisent déjà les propriétés personnalisées et l’imbrication native, les quelques mixins partagés de votre partial _mixins.scss sont peut-être la seule raison pour laquelle vous exécutez encore Sass. Cet article porte un vrai mixin vers le CSS natif, compare les deux versions et indique quels types de mixins ne se prêtent pas à la migration.

Points clés

  • Les mixins CSS natifs utilisent @mixin --name pour définir un bloc et @apply --name pour l’utiliser. Les noms sont des dashed idents (identifiants préfixés par deux tirets), et @apply remplit le rôle du @include de Sass.
  • Sass copie les déclarations d’un mixin dans chaque règle qui l’inclut, au moment de la compilation. Un mixin natif est développé par le navigateur : la feuille de style livrée ne contient donc qu’une seule définition.
  • Selon MDN, aucun navigateur ne prend en charge les mixins CSS par défaut. Ils ne font donc pas partie de Baseline, et Sass reste le choix de référence en production.
  • Les mixins qui reposent sur les maps Sass, les boucles @each ou @for, les conditions @if/@else ou des calculs effectués au build n’ont pas d’équivalent natif.

Le mixin Sass que vous avez déjà

Un mixin cover fixe un élément sur les quatre bords de son ancêtre positionné. C’est à peu près le mixin le plus simple qui soit, ce qui en fait un bon cas d’étude. Il est ici utilisé deux fois : une fois sur un arrière-plan de boîte de dialogue, une fois sur une surcouche créée par un pseudo-élément. (Pour en savoir plus côté Sass, consultez ce guide sur les mixins 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);
}

À elle seule, la définition du mixin ne produit aucun CSS. Chaque @include copie les déclarations dans la règle qui l’appelle :

.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);
}

Plus les règles incluant le mixin sont nombreuses, plus le fichier généré contient de copies de ces deux déclarations.

La réécriture en mixin CSS natif, côte à côte

Porter vers le CSS natif un mixin Sass composé uniquement de déclarations se fait en trois étapes mécaniques : ajouter deux tirets devant le nom, transformer les paramètres $ en paramètres --, et remplacer @include par @apply.

Sass

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

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

CSS natif

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

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

@apply remplace @include, et le nom du mixin doit être un dashed ident. Comme c’est le navigateur qui effectue le développement, la feuille de style livrée ne contient qu’une seule définition de --cover. Le corps du mixin se compose uniquement de déclarations, sans enveloppe @result, car le CSSWG a décidé de supprimer @result des mixins. Le brouillon de spécification lui-même avertit que les mixins sont bien moins stabilisés que les fonctions personnalisées : la syntaxe est donc susceptible d’évoluer.

Prise en charge des mixins CSS par les navigateurs

Les mixins CSS natifs ne sont pas utilisables en production. Le guide de MDN consacré aux fonctions personnalisées et aux mixins indique qu’aucun navigateur ne prend en charge les mixins par défaut, ce qui signifie que la fonctionnalité ne fait pas partie de Baseline. Chromium a une longueur d’avance sur les autres moteurs : il a publié un Intent to Prototype pour les mixins CSS, mais ces travaux restent expérimentaux.

Les mixins CSS natifs ne fonctionnent pas non plus en amélioration progressive. La gestion des erreurs en CSS ignore les constructions que le parseur ne comprend pas : un navigateur qui ne prend pas en charge la fonctionnalité ignore donc @apply, et un élément qui ne reçoit position et inset que par le biais du mixin se retrouve sans l’une ni l’autre. Conservez la version Sass en production, et expérimentez les mixins natifs dans un simple fichier .css que le compilateur Sass ne traite jamais.

Porter les paramètres des mixins

Les paramètres de mixins Sass qui transmettent de simples valeurs se portent sans difficulté vers les mixins CSS natifs. Ce n’est pas le cas des mixins qui calculent des valeurs, utilisent des conditions ou des boucles au moment du build. Voici cover avec un argument d’inset :

// 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: ""; }

Chaque paramètre devient une propriété privée à l’intérieur du corps du mixin, que l’on lit avec var(). Les autres styles de la page n’y ont pas accès. Les valeurs de repli de var() fonctionnent dans le corps du mixin comme dans n’importe quelle autre déclaration. Le brouillon CSS Custom Functions and Mixins définit également sa propre syntaxe pour typer les paramètres et leur attribuer des valeurs par défaut. Consultez le brouillon pour connaître la forme actuelle, et ne partez pas du principe qu’une valeur de repli se comporte exactement comme une valeur par défaut. Le First Public Working Draft du 15 mai 2025 exigeait des parenthèses même pour un mixin sans paramètre ; les brouillons ultérieurs permettent de les omettre. Les anciennes versions du prototype exigeaient @mixin --a() et @apply --a(); : vous rencontrerez donc les deux formes.

Les fonctionnalités de mixins Sass suivantes n’ont pas d’équivalent en CSS natif :

  • les boucles @each et @for
  • les conditions @if/@else et @error
  • les maps et map.get du module sass:map
  • les calculs effectués au build, comme math.div ou la conversion d’unités

Le mixin de breakpoints montre où se situe la limite :

@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;
  }
}

La spécification définit @contents comme l’équivalent du @content de Sass : en principe, un mixin natif peut donc recevoir un bloc de l’appelant et l’envelopper dans une règle @media. Ce qui ne se porte pas, ce sont la recherche dans la map, la branche @error et la largeur variable dans la media query.

Qu’apportent les mixins CSS natifs ?

Les mixins CSS natifs présentent deux avantages par rapport aux mixins Sass : l’absence d’étape de build, et des arguments résolus dans le navigateur plutôt qu’à la compilation. Premièrement, le navigateur lit directement @mixin et @apply : le CSS que vous écrivez est le CSS que vous livrez.

Le second avantage est plus subtil. Sass peut lui aussi générer des références var(), si bien qu’un mixin Sass peut produire des déclarations qui réagissent aux propriétés personnalisées à l’exécution. En revanche, Sass ne peut pas modifier un argument après la compilation : cover(0.5rem) devient inset: 0.5rem une fois pour toutes, pendant le build. Les mixins natifs, eux, sont résolus dans le navigateur, et la spécification prévoit la gestion d’arguments dont la valeur dépend de l’élément auquel le mixin est appliqué. Ce comportement relève de la spécification, et non d’un moteur déjà livré. Les fonctions personnalisées, fonctionnalité sœur issue du même module et définies avec @function, renvoient une valeur unique, tandis que les mixins renvoient un bloc de déclarations.

Quand faut-il conserver Sass ?

Les mixins composés uniquement de déclarations, comme cover, le centrage ou visually-hidden, se portent sans difficulté. Ceux qui génèrent des sélecteurs, parcourent des listes ou calculent des valeurs au build ne s’y prêtent pas.

MixinPortage simple ?Ce qui changePourquoi pas
Cover / insetOuiNom en --, @apply, var(--inset)s.o.
Visually-hiddenOuiNom en --, @applys.o.
CentrageOuiNom en --, @applys.o.
BreakpointsNonEnveloppement de bloc via @contents, en principeRecherche dans la map, @error, largeur variable dans la media query

Continuez à utiliser @mixin et @include de Sass pour les boucles qui génèrent des classes utilitaires, les tokens pilotés par des maps et les utilitaires de breakpoints. Et continuez à l’utiliser pour tout ce qui part en production, jusqu’à ce que les mixins soient pris en charge par défaut dans les navigateurs.

Conclusion

Convertir en syntaxe native un mixin composé uniquement de déclarations, comme cover, est rapide et essentiellement mécanique. Le moment opportun pour franchir le pas dépend de la prise en charge par les navigateurs, qui n’existe pas encore. Prochaine étape concrète : classez vos mixins en deux groupes, ceux qui ne contiennent que des déclarations et ceux qui reposent sur la logique de Sass. Réécrivez le premier groupe dans un fichier .css distinct et testez-le dans une version expérimentale de Chromium. Conservez les versions Sass jusqu’à ce que MDN indique une prise en charge par défaut.

FAQ

Comment tester les mixins CSS natifs dans Chrome ?

Le prototype de mixins de Chromium est masqué derrière le feature flag CSSMixins. Pour l'essayer, lancez Chrome Canary en ligne de commande avec l'argument --enable-features=CSSMixins. L'implémentation est incomplète et évolue au fil des décisions du CSSWG sur les questions de spécification : certains exemples peuvent donc échouer ou se comporter différemment de ce que prévoit le brouillon. Réservez-la aux expérimentations et aux retours sur la spécification, jamais aux styles de production.

Pourquoi ne puis-je pas remplacer une variable de breakpoint Sass par var() dans une media query ?

La fonction var() ne fonctionne qu'à l'intérieur des valeurs de propriétés, et les conditions de media queries ne sont pas des valeurs de propriétés. Les propriétés personnalisées sont résolues pour chaque élément via la cascade, alors qu'une media query est évaluée par rapport au viewport ou à l'appareil, et non par rapport à un élément. Une condition comme min-width: var(--md) est donc invalide, et une largeur de breakpoint stockée dans une variable ne peut pas passer de Sass à une media query native.

Un mixin CSS natif peut-il contenir des sélecteurs imbriqués ou des media queries ?

Oui, selon la spécification. Le brouillon CSS Custom Functions and Mixins autorise, dans le corps d'un mixin, les règles de style imbriquées, comme un bloc esperluette ::after, ainsi que les règles conditionnelles comme @media, à la manière d'un mixin Sass. Aucun navigateur ne prenant en charge les mixins par défaut, il s'agit du comportement prévu par la spécification et non de quelque chose qui fonctionne dans un moteur stable.

postcss-mixins est-il équivalent aux mixins CSS natifs ?

Non. Le plugin postcss-mixins définit un mixin avec @define-mixin, l'appelle avec @mixin et utilise des paramètres préfixés par le signe dollar, développés au moment du build. Le CSS natif utilise @mixin pour définir un mixin et @apply pour l'appeler, et c'est le navigateur qui le développe. Le mot-clé @mixin a donc un sens opposé dans les deux systèmes : passer de postcss-mixins à la syntaxe native implique de réécrire à la fois les définitions et les appels.

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.