En esta página

Custom properties (variables CSS)

10 min lectura TextoCap. 5 — CSS moderno

Qué son las custom properties?

Las custom properties (también llamadas variables CSS) son valores reutilizables que defines con -- y consumes con var(). A diferencia de las variables de Sass o Less, las custom properties son nativas del navegador, funcionan en tiempo de ejecución y participan en la cascada.

:root {
  --mi-color: #ff5500;
}

.elemento {
  color: var(--mi-color);
}

Definir variables

Las variables se definen dentro de cualquier selector CSS. La convención es definir las globales en :root:

:root {
  --color-primario: oklch(65% 0.2 25);
  --espaciado: 1rem;
  --font-base: system-ui, sans-serif;
}

Scope: cascada y herencia

Las custom properties heredan a los hijos, como color o font-family. Esto significa que puedes redefinir una variable en un contenedor y todos sus hijos la usaran:

:root { --bg: white; }

.sección-oscura {
  --bg: #1a1a2e; /* Solo dentro de esta sección */
}

.card {
  background: var(--bg); /* Hereda del contexto */
}

Consumir variables con var()

La función var() acepta dos argumentos: la variable y un valor de respaldo opcional:

.elemento {
  color: var(--mi-color, #333);
  /* Si --mi-color no existe, usa #333 */
}

Los fallbacks pueden ser otros var():

color: var(--color-tema, var(--color-base, black));

Variables de componente

Un patrón poderoso es definir variables privadas dentro de un componente y sobreescribirlas con modificadores:

.boton {
  --btn-bg: #1a1a2e;
  --btn-text: white;
  --btn-radius: 8px;

  background: var(--btn-bg);
  color: var(--btn-text);
  border-radius: var(--btn-radius);
  padding: 0.75rem 1.5rem;
}

.boton--primario { --btn-bg: #ff530f; }
.boton--secundario { --btn-bg: transparent; --btn-text: #1a1a2e; }
.boton--pill { --btn-radius: 999px; }

Temas con custom properties

La mayor ventaja de las custom properties es la tematizacion. Defines los tokens una vez y los cambias segun el contexto:

:root {
  --bg: white;
  --text: #1a1a2e;
  --border: #e0e0e0;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0a0a0f;
    --text: #e8e8e8;
    --border: rgb(255 255 255 / 10%);
  }
}

/* También puedes tener temas por clase */
[data-theme="alto-contraste"] {
  --bg: black;
  --text: white;
  --border: white;
}

Variables dinamicas desde HTML

Puedes pasar variables desde el HTML con style inline. Esto es útil para valores que vienen de JavaScript o datos dinámicos:

<div class="progreso" style="--valor: 75%"></div>
.progreso::after {
  width: var(--valor, 0%);
}

Variables con calc() y otras funciones

Las custom properties se pueden usar dentro de funciones CSS:

:root {
  --base: 1rem;
}

.grande {
  font-size: calc(var(--base) * 2);    /* 2rem */
  padding: calc(var(--base) * 0.5);    /* 0.5rem */
}

Custom properties vs preprocesadores

Caracteristica Custom properties Sass/Less
Ejecución Navegador (runtime) Compilacion (build)
Cascada Si No
Temas dinámicos Si No (sin JS)
Media queries Se pueden redefinir No
JavaScript Accesibles No

Las custom properties no reemplazan completamente a los preprocesadores (que ofrecen loops, mixins, funciones), pero para tematizacion y valores dinámicos son superiores.

@property: variables tipadas

Con @property puedes registrar una custom property con tipo, valor inicial y comportamiento de herencia:

@property --progreso {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 0%;
}

.barra {
  --progreso: 0%;
  background: linear-gradient(to right, #b056ff var(--progreso), #e0e0e0 0);
  transition: --progreso 500ms ease; /* Ahora se puede animar */
}

.barra:hover {
  --progreso: 100%;
}

Sin @property, las custom properties no se pueden animar con transiciones porque el navegador no conoce su tipo.


Las custom properties son la base del CSS mantenible. En la siguiente leccion exploraremos las funcionalidades más recientes de CSS en 2026.

Práctica

  1. Define un sistema de tokens: Crea variables globales en :root para colores, espaciado y border-radius. Usa esas variables en al menos 3 componentes diferentes (boton, tarjeta, badge).
  2. Crea variantes con variables de componente: Construye un componente .boton con variables internas (--btn-bg, --btn-text) y crea modificadores (.boton--primario, .boton--secundario) que solo redefinan las variables.
  3. Anima una custom property con @property: Registra una variable --progreso con @property, aplicala a un gradiente lineal y animala con transition al hacer hover.
Convencion de nombres
Usa prefijos para organizar tus variables: --color-* para colores, --space-* para espaciado, --font-* para tipografia, --radius-* para bordes redondeados. Para variables de componente, usa el nombre del componente: --card-padding, --badge-bg.
Fallback values
var() acepta un segundo argumento como valor de respaldo: var(--mi-color, #333). Si la variable no esta definida, se usa el fallback. Esto hace que tus componentes sean más robustos.
/* Definir variables globales */
:root {
  --color-brand: oklch(75% 0.18 85);
  --color-accent: oklch(55% 0.22 340);
  --color-text: oklch(15% 0.01 260);
  --color-surface: oklch(98% 0.005 260);

  --font-sans: system-ui, sans-serif;
  --font-mono: "Fira Code", monospace;

  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 16px;

  --space-xs: 0.25rem;
  --space-sm: 0.5rem;
  --space-md: 1rem;
  --space-lg: 2rem;
  --space-xl: 4rem;
}

/* Tema oscuro: solo redefinir variables */
@media (prefers-color-scheme: dark) {
  :root {
    --color-text: oklch(92% 0.01 260);
    --color-surface: oklch(12% 0.01 260);
  }
}

/* Uso en componentes */
.card {
  background: var(--color-surface);
  color: var(--color-text);
  border-radius: var(--radius-lg);
  padding: var(--space-lg);
  font-family: var(--font-sans);
}

.boton {
  background: var(--color-brand);
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-md);
  border: none;
  cursor: pointer;
}
/* Variables de componente con scope local */
.badge {
  --badge-bg: #e0e0e0;
  --badge-text: #333;
  --badge-size: 0.75rem;

  background: var(--badge-bg);
  color: var(--badge-text);
  font-size: var(--badge-size);
  padding: 0.25em 0.75em;
  border-radius: 999px;
  display: inline-block;
}

/* Variantes: solo redefinir las variables */
.badge--éxito {
  --badge-bg: oklch(85% 0.15 145);
  --badge-text: oklch(25% 0.1 145);
}

.badge--error {
  --badge-bg: oklch(85% 0.12 25);
  --badge-text: oklch(30% 0.15 25);
}

.badge--grande {
  --badge-size: 1rem;
}

/* Variables desde HTML (inline) */
.barra-progreso {
  height: 8px;
  background: #e0e0e0;
  border-radius: 4px;
  overflow: hidden;
}

.barra-progreso::after {
  content: "";
  display: block;
  height: 100%;
  width: var(--progreso, 0%);
  background: var(--color-brand, #b056ff);
  border-radius: 4px;
  transition: width 500ms ease;
}