Tema
Variables CSS
Imagina que el cliente dice: "Me gustaría probar el azul un poco más oscuro". Si escribiste #2563eb en 47 lugares, tienes un problema. Con variables CSS (custom properties) lo cambias en una sola línea. En esta lección aprenderás a crearlas, su alcance, cómo construir un sistema de diseño (design tokens) y un tema claro/oscuro completo.
Concepto
Una variable CSS es un valor con nombre que defines una vez y reutilizas donde quieras.
Analogía
Es como el contacto "Mamá" en tu teléfono: si cambia de número, lo actualizas en un solo lugar y todas tus llamadas siguen funcionando. No necesitas memorizar el número.
Sintaxis
css
/* 1. Definir: empieza con dos guiones */
:root {
--color-primario: #2563eb;
--espacio: 1rem;
--radio: 12px;
--fuente-base: "Inter", system-ui, sans-serif;
}
/* 2. Usar con var() */
.boton {
background: var(--color-primario);
padding: var(--espacio) calc(var(--espacio) * 2);
border-radius: var(--radio);
font-family: var(--fuente-base);
}:rootes el elemento raíz (html): las variables definidas ahí están disponibles en todo el documento.- Los nombres distinguen mayúsculas:
--Colory--colorson distintas. - Pueden guardar cualquier valor: colores, medidas, fuentes, sombras completas, incluso fragmentos (
--sombra: 0 4px 12px rgb(0 0 0 / .1)).
Valor de respaldo (fallback)
css
.alerta {
color: var(--color-alerta, #dc2626); /* si --color-alerta no existe, usa rojo */
padding: var(--alerta-padding, var(--espacio, 1rem)); /* respaldos anidados */
}Alcance (scope) y herencia
Las variables se heredan y siguen la cascada. Puedes redefinirlas en cualquier selector y afectarán sólo a ese elemento y sus descendientes:
css
:root {
--color-boton: #2563eb;
}
.boton {
background: var(--color-boton);
}
/* Variante: redefine la variable, no la propiedad */
.boton--peligro {
--color-boton: #dc2626;
}
/* Todos los botones dentro del footer serán grises */
.footer {
--color-boton: #475569;
}Este patrón es muy poderoso para variantes de componentes: el componente define qué propiedad usa la variable y las variantes sólo cambian el valor.
Variables como "API" de un componente
css
.tarjeta {
--tarjeta-padding: 1.5rem;
--tarjeta-fondo: #fff;
padding: var(--tarjeta-padding);
background: var(--tarjeta-fondo);
}
.tarjeta--compacta { --tarjeta-padding: 0.75rem; }
.tarjeta--destacada { --tarjeta-fondo: #fef3c7; }Incluso puedes personalizar desde el HTML: style="--tarjeta-fondo: #e0f2fe".
Variables + calc()
css
:root {
--espacio: 8px;
}
.seccion { padding: calc(var(--espacio) * 8); } /* 64px */
.tarjeta { gap: calc(var(--espacio) * 2); } /* 16px */
/* Con números sin unidad */
:root { --columnas: 3; }
.grid {
grid-template-columns: repeat(var(--columnas), 1fr);
}
@media (max-width: 600px) {
:root { --columnas: 1; } /* cambiar la variable cambia el layout */
}Error común
--tamano: 20; y luego width: var(--tamano)px; no funciona: las variables no se concatenan con texto. Usa calc(var(--tamano) * 1px) o guarda la unidad en la variable: --tamano: 20px.
Design tokens: un sistema completo
Los equipos profesionales definen todos los valores del diseño como variables ("tokens"). Así el sitio es consistente y los cambios son triviales:
css
:root {
/* Colores de marca */
--color-primario: hsl(262 83% 58%);
--color-primario-hover: hsl(262 83% 48%);
--color-secundario: hsl(174 72% 40%);
/* Colores semánticos (por función, no por apariencia) */
--color-fondo: #ffffff;
--color-superficie: #f8fafc;
--color-texto: #0f172a;
--color-texto-suave: #64748b;
--color-borde: #e2e8f0;
--color-exito: #16a34a;
--color-error: #dc2626;
/* Tipografía */
--fuente-texto: "Inter", system-ui, sans-serif;
--fuente-titulos: "Playfair Display", Georgia, serif;
--texto-sm: 0.875rem;
--texto-base: 1rem;
--texto-lg: 1.25rem;
--texto-xl: clamp(1.5rem, 1rem + 2vw, 2rem);
--texto-2xl: clamp(2rem, 1.2rem + 4vw, 3.5rem);
/* Espaciado (escala de 4/8px) */
--espacio-1: 0.25rem;
--espacio-2: 0.5rem;
--espacio-3: 1rem;
--espacio-4: 1.5rem;
--espacio-5: 2rem;
--espacio-6: 3rem;
--espacio-7: 5rem;
/* Bordes y sombras */
--radio-sm: 6px;
--radio: 12px;
--radio-full: 999px;
--sombra-sm: 0 1px 2px rgb(0 0 0 / 0.06);
--sombra: 0 4px 16px rgb(0 0 0 / 0.08);
--sombra-lg: 0 16px 40px rgb(0 0 0 / 0.12);
/* Movimiento */
--transicion: 200ms ease;
/* Layout */
--ancho-max: 1200px;
}Buena práctica: nombres semánticos
Nombra por función (--color-texto, --color-error) y no por apariencia (--gris-oscuro, --rojo). Si mañana el tema oscuro usa texto claro, --color-texto sigue teniendo sentido; --gris-oscuro no.
Tema claro y oscuro
Con tokens semánticos, un tema oscuro consiste en redefinir los valores, sin tocar ningún componente:
1. Respetar la preferencia del sistema
css
:root {
--color-fondo: #ffffff;
--color-texto: #0f172a;
--color-superficie: #f8fafc;
--color-borde: #e2e8f0;
color-scheme: light dark; /* formularios y scrollbars nativos se adaptan */
}
@media (prefers-color-scheme: dark) {
:root {
--color-fondo: #0f172a;
--color-texto: #e2e8f0;
--color-superficie: #1e293b;
--color-borde: #334155;
}
}
body {
background: var(--color-fondo);
color: var(--color-texto);
}2. Con un botón para elegir (y recordarlo)
html
<button id="tema" aria-label="Cambiar tema">🌙</button>css
/* Tema oscuro activado manualmente */
[data-theme="oscuro"] {
--color-fondo: #0f172a;
--color-texto: #e2e8f0;
--color-superficie: #1e293b;
--color-borde: #334155;
}js
const html = document.documentElement;
const boton = document.getElementById('tema');
// 1. Al cargar: preferencia guardada o, si no hay, la del sistema
const guardado = localStorage.getItem('tema');
const sistemaOscuro = window.matchMedia('(prefers-color-scheme: dark)').matches;
html.dataset.theme = guardado ?? (sistemaOscuro ? 'oscuro' : 'claro');
// 2. Al hacer clic: alterna y guarda
boton.addEventListener('click', () => {
const nuevo = html.dataset.theme === 'oscuro' ? 'claro' : 'oscuro';
html.dataset.theme = nuevo;
localStorage.setItem('tema', nuevo);
});Parpadeo al cargar
Si el script se ejecuta al final de la página, verás un destello del tema claro antes de cambiar al oscuro. Coloca la parte que lee localStorage en un script pequeño dentro del head, antes del CSS, para aplicar el tema antes de pintar.
light-dark()
Los navegadores modernos permiten definir ambos valores en una línea (requiere color-scheme):
css
:root {
color-scheme: light dark;
--color-fondo: light-dark(#ffffff, #0f172a);
--color-texto: light-dark(#0f172a, #e2e8f0);
}Variables y JavaScript
js
const raiz = document.documentElement;
// Leer
const primario = getComputedStyle(raiz).getPropertyValue('--color-primario').trim();
// Escribir
raiz.style.setProperty('--color-primario', '#16a34a');
// Ejemplo: seguir el mouse con un brillo
document.addEventListener('pointermove', (e) => {
raiz.style.setProperty('--mouse-x', e.clientX + 'px');
raiz.style.setProperty('--mouse-y', e.clientY + 'px');
});css
.brillo {
background: radial-gradient(circle at var(--mouse-x) var(--mouse-y), rgb(124 58 237 / 0.25), transparent 40%);
}Es la forma limpia de conectar JS y CSS: JavaScript sólo cambia números y el CSS decide cómo se ven.
Casos de uso reales
- Marca del cliente: todo el sitio se basa en
--color-primario. Si cambian el logo, actualizas un valor. - Multi-marca o franquicias: el mismo sitio con distintos colores según la sucursal, cambiando unas variables en una clase en el
body. - Personalización por el usuario: tamaño de fuente o tema elegidos en un panel de configuración.
- Componentes configurables en un sistema de diseño.
Ejercicios
- Define en
:rootun color primario, un radio y un espaciado, y úsalos en un botón. - Crea
.boton--secundarioy.boton--peligrocambiando sólo una variable. - Haz que una grilla tenga 1 columna en móvil y 3 en escritorio cambiando una variable
--columnasen una media query. - Implementa un tema oscuro automático con
prefers-color-schemepara fondo, texto y bordes.
Ver solución
css
/* 1 y 2 */
:root {
--color-primario: #2563eb;
--radio: 10px;
--espacio: 1rem;
}
.boton {
--boton-color: var(--color-primario);
background: var(--boton-color);
color: #fff;
border: 0;
border-radius: var(--radio);
padding: calc(var(--espacio) * 0.75) calc(var(--espacio) * 1.5);
}
.boton--secundario { --boton-color: #475569; }
.boton--peligro { --boton-color: #dc2626; }
/* 3 */
.grid {
--columnas: 1;
display: grid;
grid-template-columns: repeat(var(--columnas), 1fr);
gap: var(--espacio);
}
@media (min-width: 900px) {
.grid { --columnas: 3; }
}
/* 4 */
:root {
--fondo: #ffffff;
--texto: #111827;
--borde: #e5e7eb;
color-scheme: light dark;
}
@media (prefers-color-scheme: dark) {
:root {
--fondo: #111827;
--texto: #f3f4f6;
--borde: #374151;
}
}
body { background: var(--fondo); color: var(--texto); }
.tarjeta { border: 1px solid var(--borde); }Resumen
- Define con
--nombre: valory usa convar(--nombre, respaldo). - Se heredan y respetan la cascada: redefínelas en un selector para crear variantes.
- No se concatenan con unidades: usa
calc(var(--n) * 1px). - Los design tokens con nombres semánticos hacen el sitio consistente y fácil de cambiar.
- Tema oscuro = redefinir tokens con
prefers-color-schemeo un atributodata-theme. - JavaScript puede leer y escribir variables con
setProperty.
Siguiente: Pseudo-clases y pseudo-elementos.