Variables CSS a fondo

Más allá de declarar variables: herencia, fallbacks, ámbito local, manipulación desde JavaScript y patrones útiles para sistemas de diseño.

En el curso de Fundamentals usaste variables CSS para definir colores. Aquí las entiendes de verdad: cómo se heredan, cómo se sobreescriben, cómo se leen y cambian desde JavaScript, y cómo usarlas para construir un sistema de diseño que escale.


Repaso rápido: qué es una variable CSS

Las variables CSS (formalmente custom properties) se definen con -- y se usan con var():

:root {
  --color-primary: #3d5a80;
}

button {
  background: var(--color-primary);
}

La novedad respecto a variables de Sass o LESS: las variables CSS son propiedades reales del DOM. Viven en el árbol de elementos, se heredan, y se pueden leer y modificar con JavaScript en tiempo real.


Herencia y ámbito

Las variables CSS siguen las mismas reglas de herencia que cualquier propiedad heredable. Si defines una variable en :root, está disponible en todos los elementos. Si la defines en un componente específico, solo ese componente y sus hijos la ven:

:root {
  --color-text: #1a1a2e;      /* disponible en toda la página */
}

.card {
  --color-text: #ffffff;      /* sobreescribe solo dentro de .card */
  color: var(--color-text);   /* usa #ffffff */
}

.card p {
  color: var(--color-text);   /* hereda #ffffff de .card */
}

p {
  color: var(--color-text);   /* usa #1a1a2e de :root */
}

Esto permite crear variantes de componentes sin clases extra:

.btn {
  --btn-bg: var(--color-primary);
  --btn-color: #ffffff;
  background: var(--btn-bg);
  color: var(--btn-color);
  padding: 0.6rem 1.2rem;
}

.btn--danger {
  --btn-bg: #e63946;   /* solo cambias las variables, no repites propiedades */
}

.btn--ghost {
  --btn-bg: transparent;
  --btn-color: var(--color-primary);
}

Fallbacks

var() acepta un segundo argumento como valor de reserva si la variable no existe:

color: var(--color-text, #333);

Los fallbacks pueden anidarse para crear cadenas de reserva:

color: var(--color-brand, var(--color-primary, #3d5a80));
/* usa --color-brand, si no existe usa --color-primary, si no existe #3d5a80 */

Esto es útil en componentes que pueden usarse en diferentes contextos donde no todas las variables están definidas.


Variables inválidas vs indefinidas

Hay una distinción importante que genera confusión:

:root {
  --tamaño: hola; /* valor definido pero inválido para font-size */
}

p {
  font-size: var(--tamaño); /* no usa el fallback — usa el valor HEREDADO o initial */
}

Cuando una variable tiene un valor inválido para la propiedad donde se usa, CSS no aplica el fallback. En su lugar, usa el valor heredado o el valor inicial de la propiedad. El fallback solo se usa cuando la variable no está definida.


Variables numéricas con calc()

Las variables CSS guardan el valor completo como texto. Para hacer aritmética con unidades, combínalas con calc():

:root {
  --space: 1rem;
  --columns: 3;
}

.grid-item {
  width: calc(100% / var(--columns) - var(--space));
  padding: calc(var(--space) / 2);
}

También puedes guardar solo el número y añadir la unidad al usarla:

:root {
  --ratio: 1.5;  /* número puro, sin unidad */
}

h1 { font-size: calc(var(--ratio) * 2rem); }
h2 { font-size: calc(var(--ratio) * 1.5rem); }

Leer y modificar variables desde JavaScript

Como las variables CSS son propiedades del DOM, JavaScript puede leerlas y modificarlas en tiempo real:

// Leer una variable definida en :root
const root = document.documentElement;
const primary = getComputedStyle(root).getPropertyValue('--color-primary').trim();
// '#3d5a80'

// Cambiar una variable (afecta a todo lo que la usa)
root.style.setProperty('--color-primary', '#ee6c4d');

// Cambiar en un elemento concreto (ámbito local)
const card = document.querySelector('.card');
card.style.setProperty('--color-text', '#000000');

Esto es la base de los temas dinámicos (dark/light mode) sin recargar la página:

function setTheme(theme) {
  const root = document.documentElement;
  if (theme === 'dark') {
    root.style.setProperty('--color-bg', '#1a1a2e');
    root.style.setProperty('--color-text', '#f0f0f0');
  } else {
    root.style.setProperty('--color-bg', '#ffffff');
    root.style.setProperty('--color-text', '#1a1a2e');
  }
}

Patrón: sistema de espaciado con escala

En lugar de inventar valores al vuelo, define una escala y úsala en todo el proyecto:

:root {
  --space-base: 1rem;

  --space-1: calc(var(--space-base) * 0.25);  /* 4px */
  --space-2: calc(var(--space-base) * 0.5);   /* 8px */
  --space-3: calc(var(--space-base) * 0.75);  /* 12px */
  --space-4: var(--space-base);               /* 16px */
  --space-6: calc(var(--space-base) * 1.5);   /* 24px */
  --space-8: calc(var(--space-base) * 2);     /* 32px */
  --space-12: calc(var(--space-base) * 3);    /* 48px */
  --space-16: calc(var(--space-base) * 4);    /* 64px */
}

Si el diseño cambia la escala base, solo tocas --space-base y todo se ajusta proporcionalmente.


En la siguiente lección dominamos las pseudo-clases avanzadas: :is(), :where(), :has() y los selectores nth que dan superpoderes a los selectores del curso anterior.