Variables de CSS: ¿Por qué debería importarte?

Las variables de CSS, más conocidas como propiedades personalizadas de CSS, llegan a Chrome 49. Pueden ser útiles para reducir la repetición en CSS y también para lograr efectos potentes en el tiempo de ejecución, como el cambio de temas y la posible extensión o polyfill en futuras funciones de CSS.

Desorden de CSS

Cuando se diseña una aplicación, es común reservar un conjunto de colores de marca que se reutilizarán para mantener la coherencia de la app. Lamentablemente, repetir estos valores de color una y otra vez en tu CSS no es solo una tarea tediosa, sino que también es propenso a errores. Si en algún momento es necesario cambiar uno de los colores, se podría tomar cautela y buscar y reemplazar todo, pero en un proyecto lo suficientemente grande, esto podría volverse peligroso.

En los últimos tiempos, muchos desarrolladores acudieron a los preprocesadores de CSS, como SASS o LESS, que resuelven este problema mediante el uso de variables del preprocesador. Si bien estas herramientas aumentaron significativamente la productividad de los desarrolladores, las variables que usan tienen un inconveniente importante: son estáticas y no se pueden cambiar durante el tiempo de ejecución. Agregar la capacidad de cambiar variables durante el tiempo de ejecución no solo abre la puerta a cosas como los temas dinámicos de las aplicaciones, sino que también tiene grandes ramificaciones para el diseño responsivo y el potencial de polyfill en futuras funciones de CSS. Con el lanzamiento de Chrome 49, estas funciones ahora están disponibles en forma de propiedades personalizadas de CSS.

Resumen de las propiedades personalizadas

Las propiedades personalizadas agregan dos funciones nuevas a nuestra caja de herramientas de CSS:

  • La capacidad de un autor de asignar valores arbitrarios a una propiedad con un nombre que eligió el autor.
  • La función var(), que permite que un autor use estos valores en otras propiedades

Aquí hay un ejemplo rápido para demostrar

:root {
    --main-color: #06c;
}

#foo h1 {
    color: var(--main-color);
}

--main-color es una propiedad personalizada definida por el autor con un valor de #06c. Ten en cuenta que todas las propiedades personalizadas comienzan con dos guiones.

La función var() se recupera y se reemplaza por el valor de la propiedad personalizada, lo que genera color: #06c;, siempre y cuando la propiedad personalizada esté definida en alguna parte de la hoja de estilo, debería estar disponible para la función var.

La sintaxis puede parecer un poco extraña al principio. Muchos desarrolladores preguntan: "¿Por qué no solo usar $foo para nombres de variables?". El enfoque se eligió específicamente para que fuera lo más flexible posible y potencialmente permitiría macros $foo en el futuro. Para la historia de fondo, puedes leer esta publicación de uno de los autores de especificaciones, Tab Atkins.

Sintaxis de propiedades personalizadas

La sintaxis de una propiedad personalizada es sencilla.

--header-color: #06c;

Ten en cuenta que las propiedades personalizadas distinguen mayúsculas de minúsculas, por lo que --header-color y --Header-Color son propiedades personalizadas diferentes. Si bien pueden parecer simples a primera vista, la sintaxis permitida para las propiedades personalizadas es bastante permisiva. Por ejemplo, la siguiente es una propiedad personalizada válida:

--foo: if(x > 5) this.width = 10;

Si bien no sería útil como variable, ya que no sería válido en cualquier propiedad normal, se podría leer y ejecutar acciones sobre ella con JavaScript en el tiempo de ejecución. Esto significa que las propiedades personalizadas tienen el potencial de desbloquear todo tipo de técnicas interesantes que actualmente no son posibles con los preprocesadores de CSS actuales. Así que si piensas “bostezo, tengo SASS, entonces, ¿a quién le importa...?”, entonces, vuelve a mirar. Estas no son las variables con las que estás acostumbrado a trabajar.

La cascada

Las propiedades personalizadas siguen las reglas de cascada estándar, por lo que puedes definir la misma propiedad en diferentes niveles de especificidad

:root { --color: blue; }
div { --color: green; }
#alert { --color: red; }
* { color: var(--color); }
<p>I inherited blue from the root element!</p>
<div>I got green set directly on me!</div>
<div id="alert">
    While I got red set directly on me!
    <p>I’m red too, because of inheritance!</p>
</div>

Esto significa que puedes aprovechar las propiedades personalizadas dentro de las consultas de medios para facilitar el diseño responsivo. Un caso de uso podría ser expandir el margen alrededor de los elementos principales de sección a medida que aumenta el tamaño de la pantalla:

:root {
    --gutter: 4px;
}

section {
    margin: var(--gutter);
}

@media (min-width: 600px) {
    :root {
    --gutter: 16px;
    }
}

Es importante señalar que el fragmento de código anterior no es posible con los preprocesadores de CSS actuales, que no pueden definir variables dentro de las consultas de medios. ¡Tener esta habilidad libera mucho potencial!

También es posible tener propiedades personalizadas que deriven su valor de otras propiedades personalizadas. Esto puede ser extremadamente útil para el tema:

:root {
    --primary-color: red;
    --logo-text: var(--primary-color);
}

La función var()

Para recuperar y utilizar el valor de una propiedad personalizada, debes usar la función var(). La sintaxis de la función var() se ve de la siguiente manera:

var(<custom-property-name> [, <declaration-value> ]? )

<custom-property-name> es el nombre de una propiedad personalizada definida por el autor, como --foo, y <declaration-value> es un valor de resguardo que se usará cuando la propiedad personalizada a la que se hace referencia no sea válida. Los valores de resguardo pueden ser una lista separada por comas, que se combinará en un solo valor. Por ejemplo, var(--font-stack, "Roboto", "Helvetica"); define un resguardo de "Roboto", "Helvetica". Ten en cuenta que los valores abreviados, como los que se usan para margen y padding, no están separados por comas, por lo que un resguardo adecuado para padding se vería de la siguiente manera.

p {
    padding: var(--pad, 10px 15px 20px);
}

Con estos valores alternativos, el autor de un componente puede escribir estilos defensivos para su elemento:

/* In the component’s style: */
.component .header {
    color: var(--header-color, blue);
}
.component .text {
    color: var(--text-color, black);
}

/* In the larger application’s style: */
.component {
    --text-color: #080;
    /* header-color isn’t set,
        and so remains blue,
        the fallback value */
}

Esta técnica es especialmente útil para aplicar temas a componentes web que usan Shadow DOM, ya que las propiedades personalizadas pueden atravesar límites de sombras. El autor de un componente web puede crear un diseño inicial con valores de resguardo y exponer "hooks" de temas en forma de propiedades personalizadas.

<!-- In the web component's definition: -->
<x-foo>
    #shadow
    <style>
        p {
        background-color: var(--text-background, blue);
        }
    </style>
    <p>
        This text has a yellow background because the document styled me! Otherwise it
        would be blue.
    </p>
</x-foo>
/* In the larger application's style: */
x-foo {
    --text-background: yellow;
}

Cuando se usa var(), hay algunas cuestiones que debes tener en cuenta. Las variables no pueden ser nombres de propiedades. Por ejemplo:

.foo {
    --side: margin-top;
    var(--side): 20px;
}

Sin embargo, esto no equivale a configurar margin-top: 20px;. En cambio, la segunda declaración no es válida y se arroja como un error.

Del mismo modo, no puedes crear (de forma ingeniera) un valor en el que una variable proporcione parte de él:

.foo {
    --gap: 20;
    margin-top: var(--gap)px;
}

Una vez más, esto no equivale a configurar margin-top: 20px;. Para crear un valor, necesitas algo más: la función calc().

Compila valores con calc()

Si nunca trabajaste con ella, la función calc() es una herramienta pequeña que te permite realizar cálculos para determinar los valores de CSS. Es compatible con todos los navegadores modernos y se puede combinar con propiedades personalizadas para crear valores nuevos. Por ejemplo:

.foo {
    --gap: 20;
    margin-top: calc(var(--gap) * 1px); /* niiiiice */
}

Trabaja con propiedades personalizadas en JavaScript

Para obtener el valor de una propiedad personalizada en el tiempo de ejecución, usa el método getPropertyValue() del objeto CSSStyleDeclaration calculado.

/* CSS */
:root {
    --primary-color: red;
}

p {
    color: var(--primary-color);
}
<!-- HTML -->
<p>I’m a red paragraph!</p>
/* JS */
var styles = getComputedStyle(document.documentElement);
var value = String(styles.getPropertyValue('--primary-color')).trim();
// value = 'red'

De manera similar, para establecer el valor de una propiedad personalizada en el entorno de ejecución, usa el método setProperty() del objeto CSSStyleDeclaration.

/* CSS */
:root {
    --primary-color: red;
}

p {
    color: var(--primary-color);
}
<!-- HTML -->
<p>Now I’m a green paragraph!</p>
/* JS */
document.documentElement.style.setProperty('--primary-color', 'green');

También puedes configurar el valor de la propiedad personalizada para hacer referencia a otra propiedad personalizada en el entorno de ejecución mediante la función var() en tu llamada a setProperty().

/* CSS */
:root {
    --primary-color: red;
    --secondary-color: blue;
}
<!-- HTML -->
<p>Sweet! I’m a blue paragraph!</p>
/* JS */
document.documentElement.style.setProperty('--primary-color', 'var(--secondary-color)');

Dado que las propiedades personalizadas pueden hacer referencia a otras propiedades personalizadas en tus hojas de estilo, puedes imaginar cómo esto podría generar todo tipo de efectos de tiempo de ejecución interesantes.

Navegadores compatibles

Actualmente, Chrome 49, Firefox 42, Safari 9.1 y Safari 9.3 para iOS admiten propiedades personalizadas.

Demostración

Prueba la muestra para echar un vistazo a todas las técnicas interesantes que ahora puedes aprovechar gracias a las propiedades personalizadas.

Lecturas adicionales

Si te interesa obtener más información sobre las propiedades personalizadas, Philip Walton del equipo de Google Analytics escribió un manual sobre por qué le interesan las propiedades personalizadas. Puedes mantenerte al tanto de su progreso en otros navegadores en chromestatus.com.