Cómo usar mixins nativos de CSS en lugar de Sass
Aprende a adaptar mixins de Sass a CSS nativo con @mixin y @apply, gestionar parámetros e identificar funciones que aún requieren Sass.
Un mixin nativo de CSS es un bloque de declaraciones con nombre. Se define con @mixin --name y se inserta en una regla de estilo con @apply --name, lo que lo convierte en el equivalente en el navegador de @mixin e @include de Sass.
Si tus hojas de estilo ya usan propiedades personalizadas y anidamiento nativo, es posible que los pocos mixins compartidos de un parcial _mixins.scss sean la única razón por la que sigues ejecutando Sass. Este artículo migra un mixin real a CSS nativo, compara ambas versiones y enumera qué tipos de mixins no se pueden migrar.
Puntos clave
- Los mixins nativos de CSS usan
@mixin --namepara definir un bloque y@apply --namepara usarlo. Los nombres son dashed idents (identificadores que empiezan por dos guiones), y@applycumple la función de@includeen Sass. - Sass copia las declaraciones de un mixin en cada regla que lo incluye durante la compilación. Un mixin nativo lo expande el navegador, por lo que la hoja de estilo que se distribuye contiene una única definición.
- Según MDN, ningún navegador admite los mixins de CSS de forma predeterminada, así que no forman parte de Baseline y Sass sigue siendo la opción para producción.
- Los mixins que dependen de mapas de Sass, bucles
@eacho@for, ramificaciones@if/@elseo cálculos en tiempo de compilación no tienen equivalente nativo.
El mixin de Sass que ya tienes
Un mixin cover fija un elemento a los cuatro bordes de su ancestro posicionado. Es prácticamente el mixin más sencillo que existe, lo que lo convierte en un buen caso de prueba. Aquí se usa dos veces: una en un fondo de diálogo (backdrop) y otra en una superposición mediante un pseudoelemento. (Para más contexto sobre la parte de Sass, consulta esta guía sobre mixins de 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);
}
Por sí sola, la definición del mixin no genera CSS. Cada @include copia las declaraciones en la regla que lo invoca:
.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);
}
Cuantas más reglas incluyan el mixin, más copias de esas dos declaraciones acaban en el resultado.
La reescritura como mixin nativo de CSS, lado a lado
Migrar a CSS nativo un mixin de Sass que solo contiene declaraciones requiere tres pasos mecánicos: añadir dos guiones al principio del nombre, convertir los parámetros $ en parámetros -- y sustituir @include por @apply.
Sass
@mixin cover {
position: absolute;
inset: 0;
}
.dialog-backdrop {
@include cover;
background: rgb(0 0 0 / 0.6);
}
CSS nativo
@mixin --cover {
position: absolute;
inset: 0;
}
.dialog-backdrop {
@apply --cover;
background: rgb(0 0 0 / 0.6);
}
@apply ocupa el lugar de @include, y el nombre del mixin debe ser un dashed ident. Como es el navegador quien realiza la expansión, la hoja de estilo que distribuyes contiene una única definición de --cover. El cuerpo está formado solo por declaraciones, sin envoltorio @result, porque el CSSWG resolvió eliminar @result de los mixins. El propio borrador advierte de que los mixins están mucho menos consolidados que las funciones personalizadas, por lo que la sintaxis todavía podría cambiar.
Compatibilidad de los navegadores con los mixins de CSS
Los mixins nativos de CSS no se pueden usar en producción. La guía de MDN sobre funciones personalizadas y mixins indica que ningún navegador admite los mixins de forma predeterminada, lo que significa que la funcionalidad no forma parte de Baseline. Chromium va por delante de los demás motores: ha registrado un Intent to Prototype para los mixins de CSS, y ese trabajo es experimental.
Los mixins nativos de CSS tampoco funcionan como mejora progresiva. El manejo de errores de CSS descarta las construcciones que el analizador no entiende, así que un navegador sin soporte ignora @apply, y un elemento que recibe position e inset únicamente a través del mixin se queda sin ninguna de las dos. Mantén la versión de Sass para producción y prueba los mixins nativos en un archivo .css normal que el compilador de Sass nunca procese.
Migración de los parámetros de los mixins
Los parámetros de mixins de Sass que pasan valores simples se migran sin problemas a los mixins nativos de CSS. Los mixins que calculan valores, ramifican o iteran en tiempo de compilación, no. Este es cover con un argumento para el 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: ""; }
Cada parámetro se convierte en una propiedad privada dentro del cuerpo del mixin, y se lee con var(). El resto de estilos de la página no pueden verla. Los valores de respaldo (fallbacks) de var() funcionan en el cuerpo igual que en cualquier otra declaración. El borrador de CSS Custom Functions and Mixins también define su propia sintaxis para tipar los parámetros y asignarles valores predeterminados. Consulta el borrador para conocer la forma actual y no des por hecho que un valor de respaldo se comporta exactamente como un valor predeterminado. El First Public Working Draft del 15 de mayo de 2025 exigía paréntesis incluso en un mixin sin parámetros, mientras que los borradores posteriores permiten omitirlos. Las compilaciones de prototipo más antiguas requerían @mixin --a() y @apply --a();, así que encontrarás ambas formas.
Estas funcionalidades de los mixins de Sass no tienen equivalente nativo en CSS:
- Bucles
@eachy@for - Ramificaciones
@if/@elsey@error - Mapas y
map.getdel módulosass:map - Cálculos en tiempo de compilación, como
math.divo la conversión de unidades
El mixin de breakpoints muestra dónde está el límite:
@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 especificación define @contents como el equivalente de @content de Sass, así que, en principio, un mixin nativo puede recibir un bloque del invocador y envolverlo en @media. Lo que no se puede migrar es la búsqueda en el mapa, la rama @error y el ancho variable dentro de la media query.
¿Qué se gana con los mixins nativos de CSS?
Los mixins nativos de CSS ofrecen dos ventajas frente a los mixins de Sass: no hay paso de compilación y los argumentos se resuelven en el navegador en lugar de en tiempo de compilación. La primera: el navegador lee @mixin y @apply directamente, por lo que el CSS que escribes es el CSS que distribuyes.
La segunda ventaja es más sutil. Sass también puede generar referencias var(), así que un mixin de Sass puede emitir declaraciones que respondan a propiedades personalizadas en tiempo de ejecución. Lo que Sass no puede hacer es cambiar un argumento después de la compilación: cover(0.5rem) se convierte en inset: 0.5rem una sola vez, durante el build. Los mixins nativos se resuelven en el navegador, y la especificación contempla argumentos cuyos valores dependen del elemento al que se aplica el mixin. Ese comportamiento pertenece a la especificación, no a ningún motor ya publicado. Las funciones personalizadas, una funcionalidad hermana del mismo módulo que se define con @function, devuelven un único valor, mientras que los mixins devuelven un bloque de declaraciones.
¿Cuándo conviene seguir usando Sass?
Los mixins que solo contienen declaraciones, como cover, el centrado o visually-hidden, se migran sin problemas. Los que generan selectores, iteran sobre listas o calculan valores en tiempo de compilación, no.
| Mixin | ¿Se migra sin problemas? | Qué cambia | Por qué no |
|---|---|---|---|
| Cover / inset | Sí | Nombre con --, @apply, var(--inset) | No aplica |
| Visually-hidden | Sí | Nombre con --, @apply | No aplica |
| Centrado | Sí | Nombre con --, @apply | No aplica |
| Breakpoints | No | En principio, envoltura de bloques mediante @contents | Búsqueda en mapa, @error, ancho variable en la media query |
Sigue usando @mixin e @include de Sass para los bucles que generan clases utilitarias, los tokens basados en mapas y los helpers de breakpoints. Y sigue usándolo para todo lo que vaya a producción hasta que los navegadores incluyan los mixins de forma predeterminada.
Conclusión
Pasar a la sintaxis nativa un mixin que solo contiene declaraciones, como cover, es rápido y en gran parte mecánico. Decidir cuándo dar el salto depende de la compatibilidad de los navegadores, y esa compatibilidad todavía no existe. Un siguiente paso práctico: clasifica tus mixins en dos grupos, los que solo contienen declaraciones y los que dependen de la lógica de Sass. Reescribe el primer grupo en un archivo .css independiente y pruébalo en una compilación experimental de Chromium. Mantén las versiones de Sass hasta que MDN indique compatibilidad predeterminada.
Preguntas frecuentes
¿Cómo pruebo los mixins nativos de CSS en Chrome?
El prototipo de mixins de Chromium está detrás del feature flag CSSMixins. Para probarlo, inicia Chrome Canary desde la línea de comandos con el argumento --enable-features=CSSMixins. La implementación está incompleta y va cambiando a medida que el CSSWG resuelve cuestiones de la especificación, por lo que algunos ejemplos pueden fallar o comportarse de forma distinta a lo que indica el borrador. Úsalo solo para experimentos y para enviar comentarios sobre la especificación, nunca para estilos de producción.
¿Por qué no puedo sustituir una variable de breakpoint de Sass por var() en una media query?
La función var() solo funciona dentro de los valores de propiedades, y las condiciones de una media query no son valores de propiedades. Las propiedades personalizadas se resuelven para cada elemento a través de la cascada, mientras que una media query se evalúa respecto al viewport o al dispositivo, no respecto a un elemento concreto. Por eso una condición como min-width: var(--md) no es válida, y un ancho de breakpoint almacenado en una variable no puede trasladarse de Sass a una media query nativa.
¿Puede un mixin nativo de CSS incluir selectores anidados o media queries?
Sí, según la especificación. El borrador de CSS Custom Functions and Mixins permite reglas de estilo anidadas, como un bloque con ampersand y ::after, y reglas condicionales como @media dentro del cuerpo de un mixin, de forma muy parecida a como puede emitirlas un mixin de Sass. Ningún navegador incluye los mixins de forma predeterminada, así que esto describe el comportamiento previsto por la especificación, no algo que funcione en un motor estable.
¿Es postcss-mixins lo mismo que los mixins nativos de CSS?
No. El plugin postcss-mixins define un mixin con @define-mixin, lo invoca con @mixin y usa parámetros con el signo de dólar que se expanden en tiempo de compilación. CSS nativo usa @mixin para definir un mixin y @apply para invocarlo, y es el navegador quien lo expande. La palabra clave @mixin significa cosas opuestas en ambos sistemas, así que pasar de postcss-mixins a la sintaxis nativa implica reescribir tanto las definiciones como los puntos de invocación.
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