DÍA 10 / 2020

Una manera de aprovechar la cascada CSS con custom properties

Las custom properties nos proporcionan una manera de tener algo parecido a variables en CSS. Pero ¿estamos utilizando todo su potencial? Aquí tienes una técnica para usar custom properties para evitar repetir código y hacerlo más mantenible.


Llevamos muchos años disponiendo de “variables” en CSS gracias a preprocesadores como Sass. Pero cuando estas irrumpieron en el estándar CSS como custom properties (aunque también se las conoce como “variables CSS”), nos abrieron una nueva manera de trabajar con ellas.

En vez de ser imperativas, las custom properties son declarativas. Son como una propiedad CSS que podemos crear de la nada, y hacen uso de las reglas de cascada y herencia, tal y como esperaríamos de cualquier otra propiedad.

La sintaxis para usar custom properties es llamarlas utilizando como prefijo dos guiones --, y luego podremos acceder a su valor utilizando la función var(). Con este ejemplo se ve claro:

:root {
  --main-color: black;
}

body {
  --body-font-size: 14px;
  color: var(--main-color); /* color: black; */
  font-size: var(--body-font-size); /* font-size: 14px; */
}

Si te estás preguntando qué hace :root, es parecido a usar html, pero tiene una especificidad mayor. De esta manera, esa custom property será accesible a cualquier elemento de nuestro documento HTML.

Y además, como las custom properties se comportan como cualquier otra propiedad CSS, eso significa que pueden tener valores distintos en puntos de la cascada diferentes. Por ejemplo, dado este código:

:root {
  --title-size: 2rem;
}

h1 {
  font-size: var(--title-size);
}

.post {
  --title-size: 3rem;
}

Si luego creamos un <h1> dentro de un elemento con la clase .post, se renderizaría con un tamaño de 3rem, puesto que el valor de --title-size ha sido modificado en la cascada. Cualquier otro <h1> tendrá el valor que hemos definido en :root.

Puedes ver este ejemplo y trastear con él en Codepen.

See the Pen
Showcasing different values for a custom property in the cascade
by ladybenko (@ladybenko)
on CodePen.

Una vez que tenemos clara la influencia de la cascada en las custom properties, podemos utilizar esto a nuestro favor. Un caso de uso muy útil es evitar repetir código en variantes de estilo que damos a un componente.

Imaginemos que queremos implementar los estilos para un componente que es un mensaje que se le mostrará al usuario. Este mensaje tendrá un color de fondo y un icono, y tendremos variantes de estilo para distintos tipos de mensajes (error, confirmación ,etc.).

Sin custom properties, podríamos tener algo así (también lo puedes ver en Codepen) si utilizamos BEM:

.message {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: baseline;
  grid-column-gap: 1ch;

  margin: 1rem 0;
  padding: 0.5rem 1rem;
  background: #eee;
}

.message::before {
  content: "&#x1f4ec;";
}

.message--error {
  background: #fcc;
}

.message--error::before {
  content: "&#x274c;";
}
<aside class="message">
  <p>Lorem ipsum…</p>
</aside>

<aside class="message message—error">
  <p>Lorem ipsum…</p>
</aside>

El problema de esto es que al crear la variante de estilo .message--error no sólo tenemos repetidos pares propiedad-valor en nuestras reglas (en este caso, background: <valor> y content: <icono>), sino que tenemos que sobreescribir varias reglas (en este caso, la del pseudoelemento ::before)! Esto es problemático por varios motivos:

  • Si tenemos que añadir varias variantes de estilo, acabaremos con un código bastante largo.
  • Si más adelante añadimos nuevas propiedades a .message que queramos variar (o quitamos ya existentes), tendremos que ir variante por variante a añadir o quitar lo que haga falta.
  • Si al componente CSS añadimos reglas nuevas (por ejemplo, un .message__title), tendremos que añadir una regla nueva adicional por cada variante de estilo que tengamos.

En definitiva, tendremos un código repetitivo y tedioso de mantener.

Sin embargo, gracias a la cascada y las custom properties podemos plantearnos esto de otra manera: crearemos una custom property por cada valor que sea personalizable por las variantes de estilo. Y luego, simplemente modificaremos el valor de estas custom properties, en lugar de modificar reglas CSS enteras.

En nuestro ejemplo, tendremos una custom property para el valor de fondo, y otra para el icono.

.message {
  --message-bg-color: #eee;
  --message-icon: "&#x1f4ec;";

  background: var(--message-bg-color);

  display: grid;
  grid-template-columns: auto 1fr;
  align-items: baseline;
  grid-column-gap: 1ch;
  margin: 1rem 0;
  padding: 0.5rem 1rem;
}

.message::before {
  content: var(--message-icon);
}

.message--error {
  --message-bg-color: #fcc;
  --message-icon: "&#x274c;";
}

La gran diferencia respecto al ejemplo anterior, es que aquí únicamente necesitamos una sola regla para definir una variante de estilo (.message--error). Como bonus, nos ahorraremos repetir qué propiedades usaban esos valores de variables –es decir, no tenemos que sobreescribir manualmente el valor de background o de content.

¿Y qué pasaría si quisiéramos añadir una nueva variante? Pues añadiríamos una única regla adicional:

.message--success {
  --message-bg-color: #cfc;
  --message-icon: "&#x2705;";
}

El resultado es que tenemos un código más legible, más mantenible y más extensible. Tenéis aquí el Codepen por si queréis experimentar añadiendo nuevas variantes, o más custom properties.

See the Pen
Custom properties (DRY)
by ladybenko (@ladybenko)
on CodePen.

Espero que os haya sido de ayuda, y que podáis utilizar esta técnica en vuestros proyectos. ¡A programar!

Belén Albeza

Soy desarrolladora y actualmente trabajo en el equipo de Firefox DevTools en Mozilla. Aparte del desarrollo web, también me gusta el desarrollo de videojuegos y participo de vez en cuando en game jams. Aficionada a la fantasía y ciencia-ficción, al rock & roll y al Barça.