Saltar al contenido
sass.css · devschool

Sass, el preprocesador de CSS

Lección 43 de 50 · 13 min de lectura · Actualizado el

En esta lección
  1. ¿Qué es un preprocesador?
  2. Instalar y compilar
  3. Variables
  4. Anidamiento
  5. Parciales y @use
  6. Mixins: bloques reutilizables
  7. @extend (con precaución)
  8. Funciones de color
  9. Bucles con @each: generar clases de utilidad
  10. Organizar un proyecto: la estructura 7-1
  11. Sass frente a CSS nativo
  12. Errores frecuentes
  13. Resumen

Sass es una herramienta que te permite escribir CSS con superpoderes: variables, reglas anidadas, bloques reutilizables, bucles y funciones. Lleva casi veinte años en uso, Bootstrap está escrito con él y lo encontrarás en muchísimos proyectos profesionales y ofertas de empleo. En la lección organizar el CSS viste un resumen; aquí aprenderás a instalarlo, a compilar tus archivos, a usar sus funciones principales y a organizar un proyecto. También verás qué partes ya hace el CSS nativo y cuáles siguen siendo exclusivas de Sass, para que decidas cuándo te compensa.

¿Qué es un preprocesador?

El navegador solo entiende CSS. Un preprocesador es un programa que lee un archivo escrito en otro lenguaje (parecido a CSS, pero con más funciones) y lo traduce a CSS normal. A ese paso se le llama compilar.

estilos.scss  ──(compilador de Sass)──>  estilos.css  ──>  navegador

Tú editas el archivo .scss y enlazas en tu HTML el .css generado. El navegador nunca ve el Sass.

Sass tiene dos sintaxis:

  • SCSS (archivos .scss): usa llaves y punto y coma, como CSS. Cualquier CSS válido es SCSS válido, así que puedes empezar renombrando tu archivo. Es la que usa casi todo el mundo y la que verás aquí.
  • Sintaxis indentada (archivos .sass): sin llaves ni punto y coma, con sangría al estilo de Python. Hoy es minoritaria.

Instalar y compilar

Hay dos formas habituales de empezar.

Con la extensión de VS Code

La más sencilla para practicar: instala la extensión Live Sass Compiler en VS Code. Abre un archivo .scss, pulsa Watch Sass en la barra inferior y, cada vez que guardes, la extensión generará el .css al lado. No hace falta instalar nada más.

Con la línea de comandos

La forma profesional usa el compilador oficial, Dart Sass, que se instala con npm (necesitas Node.js):

npm install -g sass
sass --version

Para compilar un archivo una vez:

sass scss/estilos.scss css/estilos.css

Y para que se recompile solo cada vez que guardes, con --watch:

sass --watch scss/estilos.scss:css/estilos.css

El compilador genera además un archivo .css.map (source map) que permite que DevTools te muestre la línea del .scss original en vez de la del CSS generado. Si quieres el CSS minificado para publicar, añade --style=compressed.

En un proyecto con Vite, ni siquiera necesitas ejecutar el comando: instalas Sass en el proyecto (npm install -D sass), importas el .scss y Vite lo compila automáticamente.

Cuidado: verás en tutoriales antiguos node-sass o “LibSass”. Están abandonados. Usa el paquete sass (Dart Sass).

Variables

Las variables de Sass empiezan por $:

$principal: #2d6cdf;
$fuente-base: system-ui, sans-serif;
$radio: 8px;

.boton {
  background: $principal;
  border-radius: $radio;
  font-family: $fuente-base;
}

CSS generado:

.boton {
  background: #2d6cdf;
  border-radius: 8px;
  font-family: system-ui, sans-serif;
}

Fíjate en que en el CSS ya no hay variables: Sass ha sustituido cada $principal por su valor. Esa es la gran diferencia con las variables CSS, que siguen existiendo en el navegador. Lo verás en detalle al final.

Con las variables de Sass también puedes hacer cuentas:

$espacio: 8px;

.tarjeta {
  padding: $espacio * 2;      // 16px
  margin-bottom: $espacio * 3; // 24px
}

Los comentarios con // son de Sass y no aparecen en el CSS; los de /* */ sí.

Anidamiento

En Sass puedes escribir unas reglas dentro de otras, reflejando la estructura del HTML:

.menu {
  display: flex;
  gap: 16px;

  a {
    color: white;
    text-decoration: none;

    &:hover {
      text-decoration: underline;
    }
  }
}

CSS generado:

.menu {
  display: flex;
  gap: 16px;
}
.menu a {
  color: white;
  text-decoration: none;
}
.menu a:hover {
  text-decoration: underline;
}

El símbolo & representa el selector padre. Sirve para pseudoclases (&:hover), para combinar clases (&.activo) y, en Sass, para construir nombres de clase al estilo BEM:

.tarjeta {
  padding: 16px;

  &__titulo { font-size: 1.25rem; }   // .tarjeta__titulo
  &--destacada { border: 2px solid gold; } // .tarjeta--destacada
}

Esto último no funciona en CSS nativo, que también tiene anidamiento (lo viste en CSS moderno) pero no concatena texto con &.

Consejo: no anides más de tres niveles. .pagina .contenido .lista .item a span genera selectores largos, frágiles y con mucha especificidad. Si el HTML cambia un poco, el estilo deja de aplicarse.

Parciales y @use

En un proyecto real no quieres un archivo de 3.000 líneas. Sass permite repartir el código en parciales: archivos cuyo nombre empieza por guion bajo (_variables.scss, _botones.scss). El guion bajo le dice al compilador “no generes un CSS de este archivo por separado; solo se usa desde otros”.

scss/
├── _variables.scss
├── _botones.scss
└── estilos.scss      ← el archivo principal, el único que se compila
// _variables.scss
$principal: #2d6cdf;
$radio: 8px;
// _botones.scss
@use 'variables';

.boton {
  background: variables.$principal;
  border-radius: variables.$radio;
}
// estilos.scss
@use 'botones';

@use carga un parcial (sin el guion bajo ni la extensión). Lo que trae queda dentro de un espacio de nombres con el nombre del archivo: por eso se escribe variables.$principal. Así sabes de dónde viene cada variable y evitas que dos archivos choquen. Puedes acortarlo o quitarlo:

@use 'variables' as v;    // v.$principal
@use 'variables' as *;    // $principal, sin prefijo

Al compilar estilos.scss, sale un único estilos.css con todo el contenido. Una sola petición al servidor.

Verás en código antiguo @import 'variables';. Hacía algo parecido pero mezclaba todas las variables en un mismo espacio global; está obsoleto y dejará de funcionar en Sass 3. Usa @use (y @forward para reexportar, que verás en la estructura 7-1).

Mixins: bloques reutilizables

Un mixin es un bloque de declaraciones con nombre que puedes insertar donde quieras, y que acepta parámetros, como una función. Se define con @mixin y se usa con @include:

@mixin boton($fondo, $texto: white) {
  padding: 8px 16px;
  border: 0;
  border-radius: 6px;
  background: $fondo;
  color: $texto;
  cursor: pointer;
}

.boton-comprar { @include boton(#2a9d5c); }
.boton-aviso   { @include boton(#ffd166, #1f2328); }

CSS generado:

.boton-comprar {
  padding: 8px 16px;
  border: 0;
  border-radius: 6px;
  background: #2a9d5c;
  color: white;
  cursor: pointer;
}

.boton-aviso {
  padding: 8px 16px;
  border: 0;
  border-radius: 6px;
  background: #ffd166;
  color: #1f2328;
  cursor: pointer;
}

$texto: white es un valor por defecto: si no lo pasas, se usa blanco.

Mixins con @content: media queries cómodas

Uno de los usos más prácticos: un mixin que envuelve en una media query lo que le pases entre llaves. @content marca dónde se inserta ese bloque:

@mixin desde($ancho) {
  @media (min-width: $ancho) {
    @content;
  }
}

.menu {
  display: none;

  @include desde(768px) {
    display: flex;
  }
}

CSS generado:

.menu {
  display: none;
}
@media (min-width: 768px) {
  .menu {
    display: flex;
  }
}

Así cada componente lleva su versión responsive al lado, y los puntos de corte están definidos en un único sitio.

@extend (con precaución)

@extend hace que un selector herede las reglas de otro. Se suele usar con un selector de marcador (placeholder), que empieza por % y que no genera CSS por sí solo:

%mensaje {
  padding: 12px;
  border-radius: 6px;
}

.exito {
  @extend %mensaje;
  background: #e6f4ea;
}

.error {
  @extend %mensaje;
  background: #fdecea;
}

CSS generado:

.exito, .error {
  padding: 12px;
  border-radius: 6px;
}

.exito {
  background: #e6f4ea;
}

.error {
  background: #fdecea;
}

A diferencia del mixin, no copia las declaraciones: agrupa los selectores. Parece más eficiente, pero tiene problemas: mueve reglas a otra parte del archivo (cambiando el orden de la cascada), puede generar selectores enormes si extiendes algo que está anidado y no funciona dentro de media queries con selectores de fuera. Por eso muchos equipos lo evitan y usan mixins. Si lo usas, extiende solo %marcadores sencillos.

Funciones de color

Sass trae módulos con funciones. El de color es muy útil para generar variantes a partir de un color base:

@use 'sass:color';

$principal: #2d6cdf;

.boton {
  background: $principal;

  &:hover {
    background: color.adjust($principal, $lightness: -10%);  // un 10 % más oscuro
  }
}

.etiqueta {
  background: color.scale($principal, $lightness: 85%);  // muy aclarado, casi blanco
  color: $principal;
}

.borde-mezcla {
  border-color: color.mix($principal, white, 50%);  // mitad azul, mitad blanco
}
FunciónQué hace
color.adjust($c, $lightness: -10%)Suma o resta una cantidad fija a un canal
color.scale($c, $lightness: 30%)Acerca el canal a su máximo o mínimo en un porcentaje (más natural)
color.mix($a, $b, 30%)Mezcla dos colores
color.change($c, $alpha: 0.5)Pone un canal a un valor concreto

Verás en código antiguo darken($c, 10%) y lighten($c, 10%). Son las versiones viejas, ya obsoletas, de color.adjust.

Otros módulos útiles son sass:math (por ejemplo, math.div(100%, 3) para dividir, porque la barra / como división está obsoleta en Sass) y sass:map para trabajar con mapas.

Hoy CSS también puede crear variantes de un color en el navegador con color-mix() y la sintaxis de color relativo; lo tienes en colores.

Bucles con @each: generar clases de utilidad

Sass tiene mapas (pares clave: valor, como un diccionario) y bucles. Con ellos puedes generar muchas clases parecidas sin escribirlas a mano:

$espacios: (0: 0, 1: 4px, 2: 8px, 3: 16px, 4: 32px);

@each $nombre, $valor in $espacios {
  .mt-#{$nombre} {
    margin-top: $valor;
  }
}

CSS generado:

.mt-0 {
  margin-top: 0;
}

.mt-1 {
  margin-top: 4px;
}

.mt-2 {
  margin-top: 8px;
}

.mt-3 {
  margin-top: 16px;
}

.mt-4 {
  margin-top: 32px;
}

#{$nombre} es la interpolación: mete el valor de una variable dentro de un nombre de selector o de propiedad. Así es exactamente como Bootstrap genera sus clases mt-3, text-danger o bg-primary.

Otro ejemplo con colores:

@use 'sass:color';

$avisos: (exito: #2a9d5c, aviso: #e9a23b, peligro: #d62839);

@each $tipo, $color in $avisos {
  .alerta-#{$tipo} {
    border-left: 4px solid $color;
    background: color.scale($color, $lightness: 85%);
  }
}

Genera .alerta-exito, .alerta-aviso y .alerta-peligro. Si mañana añades un tipo al mapa, la clase aparece sola. Sass también tiene @for, @while e @if/@else, aunque @each es el que más se usa.

Organizar un proyecto: la estructura 7-1

Para proyectos grandes, una organización muy extendida es la 7-1: siete carpetas de parciales y un archivo principal.

scss/
├── abstracts/   variables, mixins y funciones (no generan CSS)
├── base/        reset, tipografía, estilos de etiquetas
├── components/  botones, tarjetas, formularios...
├── layout/      cabecera, pie, rejilla, menú
├── pages/       estilos de páginas concretas (inicio, contacto)
├── themes/      temas (claro, oscuro)
├── vendors/     CSS de terceros
└── main.scss    solo contiene @use de todo lo anterior

Cada carpeta puede tener un _index.scss que reexporta sus parciales con @forward, para cargar la carpeta entera de una vez:

// abstracts/_index.scss
@forward 'variables';
@forward 'mixins';
// components/_botones.scss
@use '../abstracts' as *;

.boton { background: $principal; }
// main.scss
@use 'base/reset';
@use 'layout/cabecera';
@use 'components/botones';

Para una web pequeña es excesivo: con abstracts/, base/, components/ y layout/ suele bastar. Lo importante es que cualquiera del equipo sepa dónde buscar cada cosa.

Sass frente a CSS nativo

CSS ha incorporado muchas cosas que antes solo daba Sass. ¿Sigue mereciendo la pena?

SassCSS nativo
Variables$principal: se sustituyen al compilar y desaparecen--principal: existen en el navegador, heredan y cambian en vivo
Cambiar con media queries o JavaScriptNo (ya no existen)Sí
AnidamientoSí, con &__elementoSí, pero sin concatenar nombres
Mixins con parámetrosSíNo (hay propuestas en camino)
Bucles y mapasSíNo
Funciones de colorSíSí: color-mix(), color relativo
Varios archivos en uno soloSí, con @use@import de CSS hace una petición por archivo
Necesita compilarSíNo

Lo habitual hoy es combinar los dos: variables CSS para todo lo que cambia en el navegador (colores del tema, modo oscuro) y Sass para lo que se resuelve al construir la web (mixins, bucles, unir parciales).

$colores: (principal: #2d6cdf, exito: #2a9d5c);

:root {
  @each $nombre, $valor in $colores {
    --#{$nombre}: #{$valor};
  }
}

Este bucle genera --principal y --exito como variables CSS a partir de un mapa de Sass: lo mejor de los dos mundos.

¿Cuándo usar Sass? Si el proyecto ya lo usa, si trabajas con Bootstrap y quieres personalizarlo, o si necesitas generar muchas clases. Para una web pequeña, CSS moderno suele bastar.

Errores frecuentes

  • Enlazar el .scss en el HTML. El navegador no lo entiende. Enlaza el .css generado.
  • Editar el CSS generado. Se sobrescribe en la siguiente compilación. Edita siempre el .scss.
  • Anidar demasiado, con selectores larguísimos y difíciles de sobrescribir.
  • Usar @import en lugar de @use en código nuevo.
  • Olvidar el espacio de nombres: tras @use 'variables', la variable es variables.$principal, no $principal (salvo que uses as *).
  • Esperar que una variable de Sass cambie en una media query o con JavaScript. Ya no existe en el navegador; para eso necesitas una variable CSS.
  • Abusar de @extend, que reordena el CSS y genera selectores inesperados.

Resumen

FunciónSintaxisPara qué
Compilarsass --watch entrada.scss:salida.cssGenerar CSS al guardar
Variables$principal: #2d6cdf;Reutilizar valores
Anidamiento.menu { a { ... } &:hover { ... } }Escribir menos selectores
Parciales_botones.scss + @use 'botones';Dividir en archivos
Mixins@mixin nombre($p) { } + @include nombre(x);Bloques reutilizables con parámetros
@content@include desde(768px) { ... }Media queries por componente
@extend@extend %marcador;Compartir reglas (con cuidado)
Colorcolor.adjust(), color.scale(), color.mix()Variantes de un color
Bucles@each $k, $v in $mapa { .x-#{$k} { } }Generar clases
7-1abstracts/, base/, components/…Organizar proyectos grandes

Ya tienes todas las herramientas para maquetar una web completa. Ponlas en práctica en el proyecto: landing.

Pon a prueba lo que has aprendido

[CSS] ¿Qué archivo se enlaza en el HTML al usar Sass?

[CSS] Tras @use 'variables'; ¿cómo accedes a la variable $principal?

[CSS] ¿Qué diferencia clave hay entre una variable de Sass ($color) y una variable CSS (--color)?

[CSS] ¿Para qué sirve @content dentro de un mixin?

¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.