Lección 1 de 312 min
Sass/SCSS: CSS con superpoderes
Variables, anidamiento, mixins, funciones y el sistema de módulos @use, comparados con lo que ya hace el CSS moderno.
Imagina una hoja de estilos de dos mil líneas: el mismo morado aparece en cuarenta sitios, cada botón repite las mismas seis declaraciones y cambiar el radio de las esquinas obliga a buscar y reemplazar a ciegas. Sass nació para resolver justo eso: añade a CSS variables, anidamiento, bloques reutilizables y funciones, y después lo traduce a CSS normal.
Muchas de esas ideas ya han llegado al CSS nativo, así que también verás qué sigue aportando Sass hoy y cuándo puedes prescindir de él. Si trabajas con Angular, lo tendrás cerca: al crear un proyecto, el CLI te pregunta qué formato de estilos quieres, y SCSS es una de las opciones.
Qué es Sass
Qué es un preprocesador
Un programa que traduce un lenguaje más cómodo a CSS normal antes de publicar.
- Implementación:
- Dart Sass
- Paquete npm:
- sass
Un preprocesador de CSS lee archivos escritos en un lenguaje ampliado y genera CSS que cualquier navegador entiende. El navegador nunca ve Sass: lo que llega a producción es el .css resultante. Es la misma idea que TypeScript con JavaScript.
Sass es el preprocesador más extendido; otros, como Less o Stylus, son hoy mucho menos habituales. La implementación oficial es Dart Sass, que se instala desde npm con el paquete sass; las antiguas LibSass y Node Sass están obsoletas.
En el día a día no lo ejecutas a mano: Angular CLI, Vite o Next.js compilan los archivos .scss por ti cuando los importas.
Úsalo cuando
Cuando tu proyecto o tu librería ya lo usan (muchos proyectos Angular, Bootstrap, Angular Material) o cuando necesitas generar mucho CSS repetitivo a partir de datos.
Ten en cuenta
Añade un paso de compilación y una dependencia. En el navegador depuras el CSS generado, no tu .scss; los source maps te llevan a la línea original.
# Instala Dart Sass y compila cada vez que guardes
npm install --save-dev sass
npx sass estilos/main.scss public/main.css --watchSCSS y la sintaxis indentada
Sass admite dos formas de escribir: SCSS, con llaves, y la indentada, sin ellas.
- SCSS:
- .scss, con llaves
- Indentada:
- .sass, sin llaves
SCSS (archivos .scss) usa llaves y punto y coma, igual que CSS. De hecho, todo CSS válido es SCSS válido: puedes renombrar estilos.css a estilos.scss y empezar a usar las funciones de Sass poco a poco.
La sintaxis indentada (archivos .sass) es la original: sin llaves ni punto y coma, con la indentación marcando los bloques, como en YAML. Las dos hacen exactamente lo mismo, pero casi todos los proyectos, librerías y tutoriales usan SCSS, y es la que verás en el resto de la lección.
Úsalo cuando
SCSS casi siempre: es la que entiende todo el mundo y la que usan Bootstrap y Angular Material. La indentada, solo si tu equipo ya la prefiere.
Ten en cuenta
No confundas el nombre del lenguaje (Sass) con la extensión .sass. Si alguien dice «escribimos en Sass», lo más probable es que use archivos .scss.
// tarjeta.scss
.tarjeta {
padding: 1rem;
&:hover { box-shadow: 0 4px 12px rgb(0 0 0 / 0.15); }
}
// tarjeta.sass
.tarjeta
padding: 1rem
&:hover
box-shadow: 0 4px 12px rgb(0 0 0 / 0.15)Las herramientas del lenguaje
Sass añade un puñado de piezas que se combinan entre sí. Todas se resuelven al compilar: el navegador recibe el resultado, nunca la variable, el mixin o el bucle.
Variables de Sass y custom properties
$marca se resuelve al compilar; --marca vive en el navegador y puede cambiar.
- Sass:
- $nombre, al compilar
- CSS:
- --nombre, en el navegador
Una variable de Sass empieza por $ y guarda cualquier valor: un color, un tamaño, una lista. Al compilar, Sass sustituye cada uso por su valor y la variable desaparece: en el CSS final solo queda #6d28d9.
Las custom properties de CSS (--marca) son otra cosa: existen en el navegador, se heredan por la cascada y puedes cambiarlas con una media query, una clase o JavaScript. Por eso son las adecuadas para temas y modo oscuro.
Puedes combinarlas: define la paleta con Sass y vuelca sus valores en custom properties con la interpolación #{…}.
Úsalo cuando
$variables para constantes de compilación, como los breakpoints de las media queries; custom properties para todo lo que deba cambiar en ejecución: temas, modo oscuro o ajustes por componente.
Ten en cuenta
Una variable de Sass no cambia en ejecución: redefinir $marca dentro de .oscuro solo afecta al código de ese bloque al compilar. Al revés, una custom property no funciona en la condición de una media query, y una $variable sí.
$marca: #6d28d9;
$tablet: 48rem;
:root {
--marca: #{$marca}; // vuelca el valor de Sass en el navegador
}
.oscuro {
--marca: #a78bfa; // esto sí cambia en ejecución
}
.boton {
background: var(--marca);
@media (min-width: $tablet) { // con var(--tablet) no funcionaría
padding-inline: 2rem;
}
}Anidamiento y el selector &
Escribe los selectores dentro de su padre y usa & para referirte a él.
- En CSS nativo:
- Sí, salvo pegar sufijos al &
En Sass puedes escribir una regla dentro de otra: .menu { a { … } } genera .menu a { … }. El símbolo & representa el selector padre, así que &:hover produce .menu:hover y &.activo produce .menu.activo.
Sass también permite pegar texto al & para construir nombres: con &__item o &--activo generas .menu__item y .menu--activo, muy cómodo si sigues la convención BEM (bloque, elemento, modificador). El CSS nativo ya admite anidamiento, pero no esto: en CSS, & siempre es un selector completo.
Úsalo cuando
Para agrupar los estados (:hover, :focus-visible), las variantes y las media queries de un componente junto a su regla principal.
Ten en cuenta
Anidar muchos niveles produce selectores larguísimos y muy específicos, difíciles de sobrescribir después. Una regla práctica: no pases de tres niveles.
.menu {
display: flex;
&__item { // .menu__item
padding: 0.5rem 1rem;
&:hover { background: #f4f4f5; } // .menu__item:hover
&--activo { font-weight: 600; } // .menu__item--activo
}
@media (max-width: 40rem) {
flex-direction: column; // .menu, dentro de la media query
}
}Mixins: @mixin e @include
Bloques de declaraciones reutilizables que pueden recibir parámetros.
Un mixin es un trozo de CSS con nombre que defines una vez con @mixin e insertas donde quieras con @include. Puede recibir argumentos, con valores por defecto, y contener reglas anidadas o media queries.
Con @content, además, el mixin recibe un bloque de reglas de quien lo usa: así le das nombre a tus breakpoints. Cada @include copia las declaraciones en su sitio, de modo que el CSS final las contiene tantas veces como uses el mixin.
Úsalo cuando
Cuando repites el mismo grupo de declaraciones con pequeñas variaciones, o para dar nombre a tus media queries.
Ten en cuenta
Como cada uso se copia, un mixin enorme incluido cien veces engorda el CSS. Si el bloque nunca cambia, a veces basta con una clase compartida.
@mixin desde($ancho) {
@media (min-width: $ancho) { @content; }
}
@mixin boton($fondo, $texto: white) {
padding: 0.5rem 1rem;
border-radius: 0.5rem;
background: $fondo;
color: $texto;
}
.boton-primario {
@include boton(#6d28d9);
@include desde(48rem) { padding-inline: 2rem; }
}Funciones: @function
Calculan y devuelven un valor, igual que una función de cualquier lenguaje.
Una función de Sass se declara con @function, recibe argumentos y devuelve un valor con @return. A diferencia de un mixin, no genera declaraciones: la usas dentro de una, como en font-size: rem(28px).
Sass trae además módulos de funciones integradas que se cargan con @use: sass:math para operar (math.div es la forma actual de dividir, porque la barra / como división está obsoleta), sass:color para aclarar u oscurecer colores y sass:map para trabajar con mapas.
Úsalo cuando
Para convertir unidades, calcular tamaños a partir de una escala o derivar colores de la paleta al compilar.
Ten en cuenta
Todo se calcula al compilar, con valores conocidos. Si el resultado depende del ancho de la ventana o de una custom property, usa calc(), clamp() o color-mix(), que se resuelven en el navegador.
@use "sass:math";
@use "sass:color";
$base: 16px;
@function rem($px) {
@return math.div($px, $base) * 1rem;
}
.titulo {
font-size: rem(28px); // 1.75rem
color: color.adjust(#6d28d9, $lightness: -10%); // un morado más oscuro
}El sistema de módulos: @use y @forward
Divide los estilos en archivos y comparte variables y mixins sin choques de nombres.
- Obsoleto:
- @import de Sass
Con @use "colores" cargas el archivo _colores.scss; el guion bajo indica un parcial, un archivo que no se compila por separado. Sus miembros quedan bajo un espacio de nombres (colores.$marca) y cada módulo se carga una sola vez, aunque lo uses en muchos archivos.
@forward reexporta lo que define otro módulo: así creas un archivo índice que reúne colores, tipografía y mixins, y el resto del proyecto lo carga con un solo @use. Con @use … with (…) puedes, además, configurar las variables marcadas con !default.
La antigua regla @import lo metía todo en un único espacio global y cargaba los archivos cada vez que se importaban, así que era difícil saber de dónde salía cada variable. Está obsoleta y Dart Sass la eliminará en una versión futura: en código nuevo, usa siempre @use.
Úsalo cuando
Siempre que tu código Sass ocupe más de un archivo, que es casi siempre.
Ten en cuenta
Los miembros cuyo nombre empieza por - o _ son privados del módulo. Y no confundas el @import de Sass con el de CSS nativo, que sigue siendo válido pero hace que el navegador descargue otro archivo.
// estilos/_colores.scss
$marca: #6d28d9 !default;
$fondo: #ffffff !default;
// estilos/_index.scss
@forward "colores";
@forward "mixins";
// app.scss
@use "estilos" with ($marca: #0e7490);
.cabecera {
background: estilos.$fondo;
border-bottom: 2px solid estilos.$marca;
}@extend y los placeholders
Hacer que un selector herede las reglas de otro sin copiar sus declaraciones.
@extend .aviso le dice a Sass: «aplica a este selector todo lo que tenga .aviso». En lugar de copiar las declaraciones, como haría un mixin, Sass añade tu selector a las reglas existentes: .aviso, .aviso-error { … }.
Un placeholder es un selector que empieza por % y no aparece en el CSS final salvo que alguien lo extienda. Sirve para definir una base compartida sin generar una clase que nadie usa en el HTML.
Úsalo cuando
Para relaciones de tipo «es un»: un aviso de error es un aviso con otro color. En la mayoría de los demás casos, un mixin es más predecible.
Ten en cuenta
@extend puede reordenar y multiplicar selectores de formas inesperadas, y no funciona a través de media queries: desde dentro de un @media no puedes extender algo definido fuera. Muchas guías de estilo prefieren los mixins.
%aviso {
padding: 1rem;
border-left: 4px solid;
border-radius: 0.25rem;
}
.aviso-exito { @extend %aviso; border-color: green; }
.aviso-error { @extend %aviso; border-color: crimson; }
/* CSS generado:
.aviso-exito, .aviso-error { padding: 1rem; border-left: 4px solid; … }
.aviso-exito { border-color: green; }
.aviso-error { border-color: crimson; } */Sass frente al CSS moderno
Sass hoy frente al CSS nativo
El CSS moderno ya hace mucho de lo que justificaba Sass, pero no todo.
Varias razones clásicas para usar Sass ya están en el navegador: las custom properties, el anidamiento, calc() y clamp(), color-mix() para mezclar colores y las capas @layer para ordenar la cascada. Con eso, un proyecto nuevo puede llegar muy lejos sin preprocesador.
Lo que Sass sigue aportando son herramientas de compilación: mixins y funciones propias, bucles (@each, @for) y mapas para generar clases a partir de datos, y módulos con espacios de nombres. Hay trabajo en curso para llevar funciones y mixins al CSS nativo, pero todavía no puedes contar con ellos en todos los navegadores.
También pesa el ecosistema: Bootstrap y Angular Material se personalizan con Sass, así que si los usas, lo tendrás en el proyecto de todas formas.
Úsalo cuando
Si tu framework o tu librería de componentes lo usan, o si generas mucho CSS repetitivo a partir de datos. Si solo querías variables y anidamiento, el CSS nativo ya te basta.
Ten en cuenta
Lo que deba cambiar en el navegador (temas, modo oscuro) tiene que acabar siendo una custom property, aunque su valor inicial salga de Sass.
$estados: (
"exito": #15803d,
"aviso": #b45309,
"error": #b91c1c,
);
@each $nombre, $color in $estados {
.etiqueta-#{$nombre} {
color: $color;
border: 1px solid $color;
}
}
// Genera .etiqueta-exito, .etiqueta-aviso y .etiqueta-errorNota
Si eliges Tailwind, que verás en la siguiente lección, lo habitual es no usar Sass: la documentación de Tailwind v4 avisa de que no está pensado para combinarse con preprocesadores como Sass, Less o Stylus.
Interactivo
Sass frente al CSS moderno
Activa o desactiva opciones para compararlas cara a cara.
| Característica | Sass (SCSS) | CSS moderno |
|---|---|---|
| Variables | $variable, fija al compilar | --variable, cambia en ejecución |
| Anidamiento | Sí, incluido pegar sufijos al & | Sí, con & como selector completo |
| Reutilizar bloques | @mixin, @function y @extend | Clases compartidas; los mixins nativos aún están en camino |
| Lógica (bucles, condiciones) | @each, @for, @if y mapas | Limitada: @media, @supports, @container |
| Cuándo se resuelve | Al compilar | En el navegador |
| Paso de compilación | Sí, con Dart Sass | No |
Ponlo en práctica
Saber qué es cada cosa es la mitad del trabajo. La otra mitad es reconocer cuándo usarla.
Interactivo
¿Qué usarías?
Lee cada situación y elige la opción que mejor la resuelve. Fallar aquí no cuenta: es para practicar.
Tienes la misma media query (min-width: 48rem) copiada en treinta componentes y quieres darle un nombre y poder cambiarla en un solo sitio.
Para recordar
- Sass es un preprocesador: tú escribes SCSS y al navegador solo llega el CSS que genera Dart Sass.
- Las
$variablesse resuelven al compilar; las custom properties viven en el navegador. Para temas y modo oscuro necesitas las segundas. - Usa mixins para insertar bloques de declaraciones, funciones para calcular valores y
@extendcon moderación. - Organiza el código con
@usey@forward:@importestá obsoleto. - El CSS moderno ya tiene variables y anidamiento; Sass compensa por su lógica de compilación y porque lo usan librerías como Bootstrap o Angular Material.
Comprueba lo aprendido
Cuestionario
Responde las 5 preguntas para completar la lección.
1.Compilas
$marca: #6d28d9; .boton { color: $marca; }. ¿Qué queda en el CSS final?2.¿Qué diferencia principal hay entre un mixin y una función de Sass?
3.¿Por qué se recomienda
@useen lugar de@import?4.Escribes
.boton { &--grande { padding: 1rem; } }. ¿Qué ocurre?5.Quieres que todos los componentes que usan el color de marca cambien según
prefers-color-scheme(el modo claro u oscuro del sistema), sin duplicar sus reglas. ¿Qué necesitas?