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

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

Confusión de CSS

Cuando se diseña una aplicación, es una práctica común reservar un conjunto de colores de la marca que se reutilizarán para mantener la apariencia de la app coherente. Lamentablemente, repetir estos valores de color una y otra vez en tu CSS no solo es una tarea tediosa, sino que también es propensa a errores. Si, en algún momento, se necesita cambiar uno de los colores, puedes dejar de lado la precaución y usar la función “Buscar y reemplazar” para todo, pero, en un proyecto lo suficientemente grande, esto podría ser peligroso.

En los últimos tiempos, muchos desarrolladores recurrieron a los preprocesadores de CSS, como SASS o LESS, que resuelven este problema mediante el uso de variables de preprocesador. Si bien estas herramientas aumentaron enormemente la productividad de los desarrolladores, las variables que usan tienen un inconveniente importante, que es que 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 las puertas a elementos como los temas dinámicos de la aplicación, sino que también tiene importantes ramificaciones para el diseño responsivo y el potencial de polyfillar 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:

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

Este es 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 que la propiedad personalizada se defina en algún lugar de tu 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 usar $foo para los nombres de las variables?" El enfoque se eligió específicamente para que sea lo más flexible posible y, en el futuro, permita macros $foo. Para conocer la historia de fondo, puedes leer esta publicación de uno de los autores de la especificación, 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 esto no sería útil como variable, ya que no sería válida en ninguna propiedad normal, podría leerse y aplicarse 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. Por lo tanto, si estás pensando “bostezo Tengo SASS, ¿a quién le importa…?”, tómate un segundo para revisarlo. Estas no son las variables con las que estás acostumbrado a trabajar.

La cascada

Las propiedades personalizadas siguen 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 ayudar con el diseño responsivo. Un caso de uso podría ser expandir los márgenes alrededor de los elementos de sección principales 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 capacidad desbloquea mucho potencial.

También es posible tener propiedades personalizadas que deriven su valor de otras propiedades personalizadas. Esto puede ser muy útil para crear temas:

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

La función var()

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

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

En la que <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 de abreviatura, como los que se usan para el margen y el padding, no están separados por comas, por lo que un resguardo adecuado para el padding se vería de la siguiente manera.

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

Con estos valores de resguardo, 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 los límites de la sombra. Un autor de componentes 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 algunos errores 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 descarta como un error.

Del mismo modo, no puedes (de forma ingenua) crear 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().

Cómo crear valores con calc()

Si nunca antes trabajaste con ella, la función calc() es una herramienta útil que te permite realizar cálculos para determinar valores de CSS. Se admite en 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 */
}

Cómo trabajar con propiedades personalizadas en JavaScript

Para obtener el valor de una propiedad personalizada en el entorno 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'

Del mismo modo, para establecer el valor de la propiedad personalizada en el tiempo 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 establecer el valor de la propiedad personalizada para que haga referencia a otra propiedad personalizada durante el tiempo de ejecución con 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 interesantes del tiempo de ejecución.

Navegadores compatibles

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

Demostración

Prueba el ejemplo para obtener una vista previa de 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 instructivo sobre por qué está entusiasmado con las propiedades personalizadas, y puedes hacer un seguimiento de su progreso en otros navegadores en chromestatus.com.