Saltar al contenido
variables.css · devschool

Variables CSS (custom properties)

Lección 18 de 25 · 9 min de lectura · Actualizado el

En esta lección
  1. Declarar y usar una variable
  2. Ámbito y herencia
  3. Tema claro y oscuro
  4. Cambiar variables desde JavaScript
  5. Variables y calc()
  6. Variables CSS frente a variables de Sass
  7. @property: variables con tipo
  8. Errores frecuentes
  9. Resumen

En una web real, el mismo azul aparece en decenas de reglas: botones, enlaces, títulos, bordes. Si un día cambia el color de la marca, tienes que buscarlo y sustituirlo en todas partes. Las variables CSS (su nombre oficial es custom properties, propiedades personalizadas) resuelven esto: guardas el valor una vez y lo reutilizas donde quieras. Pero son mucho más que un “buscar y reemplazar”: heredan, se pueden redefinir en cada parte de la página y se pueden cambiar en vivo con JavaScript. Con ellas harás un modo oscuro en pocas líneas.

Declarar y usar una variable

Una variable se declara como cualquier propiedad, pero su nombre empieza por dos guiones. Se usa con la función var():

:root {
  --color-principal: #2d6cdf;
  --color-texto: #1b2d34;
  --radio: 8px;
}

h1 {
  color: var(--color-principal);
}

.boton {
  background: var(--color-principal);
  border-radius: var(--radio);
}

Detalles importantes:

  • El nombre distingue mayúsculas y minúsculas: --Color y --color son variables distintas.
  • Puede guardar cualquier valor: un color, una medida, una lista de fuentes, una sombra entera.
  • :root es el elemento raíz del documento (<html>). Declararlas ahí las hace disponibles en toda la página.
:root {
  --fuente-titulos: Georgia, "Times New Roman", serif;
  --sombra-suave: 0 1px 2px rgb(0 0 0 / 0.08), 0 8px 24px rgb(0 0 0 / 0.08);
  --transicion: 0.2s ease-out;
}

.tarjeta {
  box-shadow: var(--sombra-suave);
  transition: box-shadow var(--transicion);
}

Valor por defecto

var() acepta un segundo argumento: el valor que se usa si la variable no existe:

.aviso {
  color: var(--color-aviso, #e76f51);  /* si no hay --color-aviso, naranja */
}

.boton {
  /* Se pueden encadenar: primero --color, si no --principal, si no azul */
  background: var(--color, var(--principal, blue));
}

Todo lo que va después de la primera coma es el valor por defecto, incluidas otras comas: var(--fuente, Arial, sans-serif) usa Arial, sans-serif si falta --fuente.

Ámbito y herencia

Aquí está la gran diferencia con las variables de otros lenguajes. Una variable CSS se hereda como color o font-family: la declaras en un elemento y la ven él y todos sus descendientes.

:root {
  --principal: #2d6cdf;   /* global: toda la página */
}

.zona-ofertas {
  --principal: #e76f51;   /* dentro de esta zona, el principal es naranja */
}

.boton {
  background: var(--principal);
}
<button class="boton">Azul</button>

<section class="zona-ofertas">
  <button class="boton">Naranja</button>   <!-- hereda el valor de la sección -->
</section>

La regla del botón es la misma; lo que cambia es el valor que hereda según dónde esté. Esto permite hacer variantes de un componente sin repetir CSS.

Variables como “parámetros” de un componente

.boton {
  --color: var(--principal);   /* valor por defecto del componente */
  padding: 8px 16px;
  background: var(--color);
  border: 2px solid var(--color);
  color: white;
}

.boton.peligro { --color: #d62839; }
.boton.exito   { --color: #2a9d5c; }

Cada variante solo cambia una variable, y el fondo y el borde se actualizan solos. Sin variables tendrías que repetir las dos propiedades en cada variante.

Consejo: declara en :root los valores globales (colores de marca, espaciados, fuentes) y en cada componente sus variables locales. Así se lee enseguida qué es global y qué no.

Tema claro y oscuro

Con variables, un modo oscuro consiste en redefinir unas pocas variables. El resto del CSS no cambia.

Siguiendo la preferencia del sistema

La media query prefers-color-scheme detecta si el usuario tiene el sistema operativo en modo oscuro:

:root {
  --fondo: #ffffff;
  --texto: #1b2d34;
  --tarjeta: #f3f5f9;
  --principal: #2d6cdf;
}

@media (prefers-color-scheme: dark) {
  :root {
    --fondo: #12181f;
    --texto: #e3e8ef;
    --tarjeta: #1d2630;
    --principal: #6f9cf0;   /* un azul más claro para que contraste sobre oscuro */
  }
}

body {
  background: var(--fondo);
  color: var(--texto);
}
.tarjeta {
  background: var(--tarjeta);
}

Con una clase que activa el usuario

Muchas webs ofrecen además un botón para elegir el tema. La forma habitual es poner una clase en <html>:

:root.oscuro {
  --fondo: #12181f;
  --texto: #e3e8ef;
  --tarjeta: #1d2630;
  --principal: #6f9cf0;
}
const boton = document.querySelector('#cambiar-tema');

boton.addEventListener('click', () => {
  document.documentElement.classList.toggle('oscuro');
});

Un paso más, que se suele añadir: guardar la elección en localStorage para que se recuerde en la siguiente visita. Lo tienes en Web Storage.

Consejo: añade color-scheme: light dark; en :root. Así el navegador también adapta al tema los controles de formulario y las barras de scroll.

Cambiar variables desde JavaScript

Las variables CSS existen mientras la página funciona (no solo al escribir el código), así que JavaScript puede leerlas y cambiarlas. Todos los elementos que las usan se actualizan al instante.

const raiz = document.documentElement;   // el <html>, donde está :root

// Cambiar (o crear) una variable
raiz.style.setProperty('--principal', '#8e44ad');

// Leer el valor que tiene en ese momento
const valor = getComputedStyle(raiz).getPropertyValue('--principal');
console.log(valor);   // #8e44ad

// Quitar la que pusimos desde JavaScript
raiz.style.removeProperty('--principal');

Un ejemplo útil: seguir la posición del ratón para un efecto de luz en una tarjeta. JavaScript solo pasa los números; el dibujo lo hace el CSS:

const tarjeta = document.querySelector('.tarjeta');

tarjeta.addEventListener('pointermove', (e) => {
  const caja = tarjeta.getBoundingClientRect();
  tarjeta.style.setProperty('--x', `${e.clientX - caja.left}px`);
  tarjeta.style.setProperty('--y', `${e.clientY - caja.top}px`);
});
.tarjeta {
  background: radial-gradient(circle at var(--x, 50%) var(--y, 50%), #ffffff40, transparent 40%), #2d6cdf;
}

Así el reparto de tareas queda limpio: JavaScript calcula, CSS pinta. Para saber más de eventos, mira eventos en JavaScript.

Variables y calc()

Las variables se combinan con calc() para derivar unos valores de otros:

:root {
  --espacio: 8px;
}

.tarjeta {
  padding: calc(var(--espacio) * 2);           /* 16px */
  margin-bottom: calc(var(--espacio) * 3);     /* 24px */
  gap: var(--espacio);                         /* 8px */
}

Si cambias --espacio a 10px, todos los espaciados crecen en proporción. Es la base de un sistema de diseño coherente.

Números sin unidad

Una variable puede guardar un número puro y convertirlo en medida al usarlo. Se multiplica por 1px, 1rem, 1deg…:

.barra {
  --progreso: 65;                           /* un número de 0 a 100 */
  width: calc(var(--progreso) * 1%);        /* 65% */
}

.icono {
  --giro: 45;
  transform: rotate(calc(var(--giro) * 1deg));   /* 45deg */
}

También puedes guardar partes de un color. Con hsl es muy cómodo crear toda una paleta a partir de un tono:

:root {
  --tono: 220;
  --principal: hsl(var(--tono) 70% 50%);
  --principal-claro: hsl(var(--tono) 70% 92%);
  --principal-oscuro: hsl(var(--tono) 70% 30%);
}

Cambiando solo --tono a 150, toda la paleta pasa de azul a verde. Tienes más sobre hsl en colores.

Cuidado: var() no pega texto. --tamanio: 20; seguido de width: var(--tamanio)px; no da 20px: el navegador ve 20 px separados y la declaración falla. Usa calc(var(--tamanio) * 1px).

Variables CSS frente a variables de Sass

Si has visto Sass (un preprocesador que añade funciones al CSS), conocerás sus variables con $:

$principal: #2d6cdf;
.boton { background: $principal; }

Se parecen, pero funcionan de forma muy distinta:

Variables de Sass ($var)Variables CSS (--var)
Cuándo existenSolo al compilar: en el CSS final ya no estánEn el navegador, mientras la página funciona
Se heredan por el HTMLNoSí, como cualquier propiedad heredable
Cambiar por zonas de la páginaNoSí, redefiniéndolas en un selector
Cambian con media queriesNo (se sustituyen antes)Sí
Cambiar con JavaScriptNoSí, con setProperty
Necesitan herramientasSí, un compiladorNo, CSS puro

Hoy, para colores, espaciados y temas, las variables CSS son la opción natural. Sass sigue siendo útil para otras cosas (bucles, mixins), y los dos tipos pueden convivir.

@property: variables con tipo

Por defecto, para el navegador una variable es solo un trozo de texto: no sabe si es un color o una medida. Por eso no puede animarla; si pones una transición sobre --angulo, salta de golpe de un valor a otro.

@property registra la variable con un tipo, un valor inicial y si se hereda:

@property --angulo {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

.borde-animado {
  background: conic-gradient(from var(--angulo), #2d6cdf, #e76f51, #2d6cdf);
  animation: girar 3s linear infinite;
}

@keyframes girar {
  to { --angulo: 360deg; }
}

Como el navegador ya sabe que --angulo es un ángulo, puede calcular los valores intermedios y el degradado gira suavemente. Los tipos más usados en syntax son <length>, <color>, <number>, <percentage> y <angle>. Las animaciones las verás en transiciones y animaciones.

Errores frecuentes

  • Olvidar los dos guiones. var(principal) o var(-principal) no funcionan. Siempre --principal.
  • Olvidar var(). color: --principal; no es válido. Hay que escribir color: var(--principal);.
  • Pegar unidades detrás. var(--n)px no funciona. Usa calc(var(--n) * 1px).
  • Esperar que una variable inválida use la regla anterior. Si --color vale 20px y escribes color: var(--color), la declaración no vuelve al valor de otra regla: el color pasa a ser el heredado (o el inicial). Por eso conviene dar buenos valores por defecto.
  • Declarar la variable en un elemento hermano. Una variable solo la ven el elemento y sus descendientes. Si la declaras en .menu, el <main> de al lado no la tiene.
  • Confundir mayúsculas. --Principal y --principal son distintas.
  • Crear variables para todo. Una variable que se usa una sola vez no aporta nada. Úsalas para valores que se repiten o que cambian (temas, variantes).

Resumen

ConceptoPara qué sirveEjemplo
--nombre: valorDeclarar una variable--principal: #2d6cdf;
var(--nombre)Usarlacolor: var(--principal);
var(--a, valor)Valor por defectovar(--color, blue)
:rootVariables globales:root { --radio: 8px; }
HerenciaRedefinir por zonas.ofertas { --principal: orange; }
prefers-color-schemeTema según el sistema@media (prefers-color-scheme: dark)
Clase en <html>Tema elegido por el usuario:root.oscuro { ... }
setProperty()Cambiarla con JavaScriptraiz.style.setProperty('--x', '10px')
calc()Derivar valorescalc(var(--espacio) * 2)
@propertyVariable con tipo (animable)syntax: "<angle>"

Con las variables tu CSS se vuelve mucho más fácil de mantener. Ahora ya puedes darle movimiento con transiciones y animaciones.

Pruébalo tú

Cambia el código y pulsa Ejecutar (o Ctrl + Enter).

variables.html
Resultado

Pon a prueba lo que has aprendido

[CSS] ¿De qué color es el fondo de cada botón?
:root { --principal: blue; }
.ofertas { --principal: orange; }
.boton { background: var(--principal); }

<!-- HTML -->
<button class="boton">A</button>
<section class="ofertas">
  <button class="boton">B</button>
</section>

[CSS] ¿Qué ancho tiene .barra?
.barra {
  --tamanio: 200;
  width: var(--tamanio)px;
}

[CSS] ¿Qué color de texto tiene el párrafo si en ningún sitio se ha declarado --aviso?
p { color: var(--aviso, var(--texto, green)); }
:root { --texto: #333; }

[CSS] ¿Qué pueden hacer las variables CSS (--var) que las variables de Sass ($var) no pueden?

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