Sass, el preprocesador de CSS
En esta lección
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-sasso “LibSass”. Están abandonados. Usa el paquetesass(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 spangenera 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ón | Qué 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?
| Sass | CSS nativo | |
|---|---|---|
| Variables | $principal: se sustituyen al compilar y desaparecen | --principal: existen en el navegador, heredan y cambian en vivo |
| Cambiar con media queries o JavaScript | No (ya no existen) | Sí |
| Anidamiento | Sí, con &__elemento | Sí, pero sin concatenar nombres |
| Mixins con parámetros | Sí | No (hay propuestas en camino) |
| Bucles y mapas | Sí | No |
| Funciones de color | Sí | Sí: color-mix(), color relativo |
| Varios archivos en uno solo | Sí, con @use | @import de CSS hace una petición por archivo |
| Necesita compilar | Sí | 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
.scssen el HTML. El navegador no lo entiende. Enlaza el.cssgenerado. - 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
@importen lugar de@useen código nuevo. - Olvidar el espacio de nombres: tras
@use 'variables', la variable esvariables.$principal, no$principal(salvo que usesas *). - 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ón | Sintaxis | Para qué |
|---|---|---|
| Compilar | sass --watch entrada.scss:salida.css | Generar 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) |
| Color | color.adjust(), color.scale(), color.mix() | Variantes de un color |
| Bucles | @each $k, $v in $mapa { .x-#{$k} { } } | Generar clases |
| 7-1 | abstracts/, 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
¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.