Listado

Cascade Layers

Cascade Layers es una manera de organizar los estilos de nuestras aplicaciones separándolos por capas que se apilan unas sobre otras para componer con más control.

Autor/a

Fecha de publicación

12/6/2023

Compartir

Twitter

LinkedIn

Son capas independientes pero permeables, con su propia cascada y lógica de especificidad, que se apilan unas sobre otras, lo que nos permite mantener nuestro código más estructurado, ordenado y fácil de localizar.

Es como si dividiésemos nuestro CSS en diferentes hojas de estilo y las incluyésemos en el documento una a continuación de la anterior con la etiqueta [.rr-code]<link>[.rr-code] o [.rr-code]<style>[.rr-code], pero con un funcionamiento diferente por las reglas de especificidad propias de las capas.

En realidad, ya trabajamos con el concepto de capas como estilos que se apilan, puesto que los estilos que los desarrolladores (autores) creamos, además de las preferencias del usuario, se aplican encima de los estilos que los navegadores dan a las etiquetas HTML.

Pero con Cascade Layers vamos a tener un control más fino, flexible y granular ya que podremos organizarlos en orden de importancia, de manera que los estilos más específicos tengan prioridad sobre los estilos más generales.

Qué problemas resuelven

  1. Organización de los estilos según abstracciones representativas.
  2. Facilita sobrescribir estilos.

Al poder separar nuestros estilos en capas podemos organizarlos mejor, de forma más intuitiva y con un control más fino. Además, nos facilita la tarea del viejo y conocido problema  de sobrescribir estilos lidiando con la especificidad, por lo que es una solución perfecta para trabajar en grandes proyectos.

Qué problemas persisten

  1. La encapsulación nativa de estilos

Las capas son permeables, no hay encapsulación y, por lo tanto, unas pueden sobrescribir a otras, lo que nos puede llevar a obtener resultados no deseados si dejamos de prestar atención a la hora de escribir nuestras clases. Es por eso que BEM (por poner un ejemplo de nomenclatura) sigue siendo un enfoque muy útil para evitar colisiones.

La encapsulación de estilos por componentes está resuelta, por ejemplo, cuando utilizamos frameworks de desarrollo que traen consigo CSS modules o CSS-in-JS, pero estas soluciones pueden llevarnos a un exceso de confianza y a repetir nombres de clases en diferentes componentes. Por lo tanto, hay que recordar que al no existir (todavía) encapsulación nativa de CSS (salvo que usemos web components), debemos siempre prestar atención al escribir nuestras clases y elegir una metodología común para el equipo.

Cómo trabajar con capas

Creando las capas

Las capas se declaran creando un nuevo bloque de estilos utilizando la directiva [.rr-code]@layer[.rr-code] y pueden ser anónimas o tener nombre.


/* Capa anónima */
@layer {
	p {
		color: red;
	}
  
	@media (min-width: 457px) {
		a { 
			color: pink; 
		}
	}
}

/* Capa con nombre */
@layer layout {
	.container {
		width: max(100%, 60rem);
	}
}
 

Ordenando las capas

Una vez creadas las capas, el orden de aparición es importante, ya que la última capa tiene mayor relevancia y sobrescribirá a la anterior. Hasta ahora, es el funcionamiento esperado ya que es como se comporta CSS.


<div class="rectangle"></div>

/* menor relevancia */
@layer default {
	.rectangle {
		background-color: coral;
	}
}

/* mayor relevancia */
@layer theme {
	.rectangle {
		background-color: lime;
	}
}
 

En el ejemplo anterior, [.rr-code].rectangle[.rr-code] será de color [.rr-code]lime[.rr-code]. Sin embargo, el orden de apilamiento puede ser alterado si lo declaramos utilizando [.rr-code]@layer[.rr-code] seguido (y separado por comas) de los nombres de las capas creadas, siendo la primera la de menor relevancia y la última la de mayor. Es conveniente que esta declaración se produzca al principio del documento.


<div class="rectangle"></div>

@layer default, theme

@layer theme {
	.rectangle {
		background-color: lime;
	}
}

@layer default {
	.rectangle {
		background-color: coral;
	}
}
 

En este caso, [.rr-code].rectangle[.rr-code] seguirá siendo [.rr-code]lime[.rr-code] porque al ordenarlas [.rr-code]theme[.rr-code] ha sido declarada después que [.rr-code]default[.rr-code]. De esta manera, podemos controlar el estilado final reordenando las capas sin tener que confiar en el orden en el que fueron escritas.

Fusionando capas

Los nombres de las capas pueden repetirse, pero hay que tener en cuenta que el navegador las fusionará entre sí para formar una única capa.


@layer theme {
	.rectangle {
		background-color: lime;
	}
}

/* 500 líneas de CSS después */

@layer theme {
	.rectangle {
		background-color: coral;
		border: 1px solid;
	}
}
 

Así en el ejemplo anterior, [.rr-code].rectangle[.rr-code] será de color [.rr-code]coral[.rr-code] y tendrá un borde de un [.rr-code]1px[.rr-code].

Anidando capas

Las capas se pueden anidar, por lo tanto podemos establecer relaciones lógicas y semánticas entre ellas, y referenciarlas para modificarlas o extenderlas más adelante.


@layer theme {
	@layer base {
		.rectangle {
			padding: 1rem;
		}
	}
}

/* ... */

@layer theme.base {
	.rectangle {
		padding: 0 1rem;
		color: white;
	}
}
 

Importando ficheros externos a capas

Una funcionalidad muy interesante es la de poder envolver ficheros enteros en capas para poder organizarlas mejor. Podríamos por ejemplo, envolver librerías de terceros y declararlas con una relevancia media o baja para que las próximas capas puedan sobrescribir sus estilos fácilmente, siempre que no contengan [.rr-code]!important[.rr-code].


@import url(https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.0.2/css/bootstrap.min.css) layer(bootstrap);

@layer default, reset, bootstrap, components, theme;
 

Importando CSS externo usando [.rr-code]<link>[.rr-code]

Actualmente no se puede envolver un archivo en una capa usando la etiqueta [.rr-code]<link>[.rr-code], sin embargo ya existe una propuesta formal de Miriam Suzanne para que se incluya como funcionalidad. En este momento la importación se hace usando [.rr-code]@import[.rr-code] pero estas regla es bloqueante, ya que mientras se resuelve la petición la carga de la página queda bloqueada, mientras que [.rr-code]<link>[.rr-code] no bloquea la lectura del código por el navegador.


<!-- styles imported into to the <layer-name> layer -->
<link rel="stylesheet" href="example.css" layer="<layer-name>">

<!-- styles imported into to a new anonymous layer -->
<link rel="stylesheet" href="example.css" layer>

<!-- with fallback for old browsers -->
<link rel="stylesheet" layer="bootstrap" media="supports(at-rule(@layer))" href="bootstrap.css">
 

Cómo se relacionan las capas con el resto de estilos

Los estilos que renderiza el navegador son el resultado de su proceso interno de ordenamiento según su relevancia, lo que llamamos cascada.

La cascada toma una lista desordenada de valores declarados para una propiedad de un elemento concreto, los ordena según su precedencia y devuelve un único valor. - W3C

Los navegadores siguen este esquema (simplificado) para determinar la cascada:

  1. Origen e importancia (a. Estilos del navegador, b. Preferencias del usuario, c. Estilos del autor (desarrollador) - Estilos con [.rr-code]!important[.rr-code])
  2. Contexto
  3. Estilos declarados mediante el atributo [.rr-code]style[.rr-code]
  4. Capas
  5. Especificidad de los selectores
  6. Orden de aparición en el código

La especificidad en las capas

Dentro de una capa, la especificidad funciona tal y como estamos acostumbrados. Los selectores más específicos van a sobrescribir a los menos específicos.

Si dudas de la especificidad de los selectores, echa un vistazo a esta calculadora.

Sin embargo, si usamos cascade layers no importa cómo de específico sea un selector si en alguna capa posterior se le aplican estilos diferentes, aunque sea con un selector menos específico.


<section 
	id="my-id"
	class="my-class">...</section>

@layer theme {
	section#my-id.my-class {
		padding: 1rem;
		background: teal;
	}
}


@layer overrides {
	section {
		color: white;
		background: coral;
	}
}
 

En este ejemplo, a pesar de que el selector de la capa [.rr-code]theme[.rr-code] tiene un peso (1, 1, 1) y el de la capa [.rr-code]overrides[.rr-code] sólo (0, 0 , 1) el color de fondo será [.rr-code]coral[.rr-code] porque la capa [.rr-code]overrides[.rr-code] está declarada después 🤯.

Una vez que se evalúan las capas y una es la “ganadora” no se continua evaluando la especificidad o el orden de aparición para el resto de las capas, así que no necesitarás volver a preocuparte si un selector se repite en diferentes capas y tiene diferente especificidad.

Estilos declarados fuera de una capa

Por otro lado, un factor a tener en cuenta es que las capas tienen menor relevancia que los estilos declarados fuera de ellas, aunque estén escritos antes que las capas, y es porque los estilos fuera de las capas se interpretan como parte de una última capa final, y por eso prevalecen.


<section 
	id="my-id"
	class="my-class">...</section>

section {
	background: aqua;
}

@layer theme {
	section#my-id.my-class {
		padding: 1rem;
		background: teal;
	}
}


@layer overrides {
section {
		color: white;
		background: coral;
	}
}

En el ejemplo anterior, el color de fondo de [.rr-code]section[.rr-code] es [.rr-code]aqua[.rr-code] a pesar de lo poco específico de su selector e incluso de estar declarado antes que las capas.

!important

Por último, hay que tener en cuenta que una capa con menor relevancia pero que contenga un valor para una propiedad con [.rr-code]!important[.rr-code] prevalecerá. Es decir, tendrá más peso que una capa más relevante, anulando el funcionamiento que hemos descrito hasta ahora.

Así que, la primera capa (y menos relevante) con un [.rr-code]!important[.rr-code] sobrescribirá capas posteriores, no importa si en ellas también se utiliza [.rr-code]!important[.rr-code] para intentar sobrescribir la capa anterior. Dado este comportamiento, una idea interesante es la declarar aquellos valores que no queremos que sean sobrescritos lo más próximo al inicio del documento.


<section 
	id="my-id"
	class="my-class">...</section>

@layer defaults, theme, overrides;

@layer defaults {
	section {
		background: lime !important;
	} 
}

section {
	background: aqua !important;
}

@layer theme {
	section#my-id.my-class {
		background: teal !important;
	}
}


@layer overrides {
	section {
		background: coral !important;
	}
}

En el ejemplo anterior, el color de fondo de section será [.rr-code]lime[.rr-code].

Soporte

De acuerdo a caniuse, el soporte es casi total, así que podemos utilizarlo sin problema.

Cómo usarlo en proyectos reales

Cascade Layers encaja perfectamente en cualquier tipo de proyecto, y no necesariamente tiene que ser un proyecto exclusivo con este enfoque, puedes añadir alguna capa a tu proyecto con CSS habitual, pero puede ser especialmente adecuado para tareas concretas como:

  • Escribir hojas de estilo tipo reset o estilos base de la aplicación
  • Sobrescribir librerías frameworks de CSS o librerías de terceros
  • Agrupar estilos por componentes
  • Aplicar temificados
  • Crear modo oscuro y modo claro
  • Agrupar estilos para los estados de algunos elementos

@layer button, states;

@layer button {
	.button {
		padding: 1rem;
		border: 0;
		color: #282828;
		background-color: #36f5bf;
	}
}

@layer states {
	:hover {
		color: #fff;
		background-color: transparent;
	}
  
	:focus-visible {
		outline: max(2px, 0.08em) solid currentColor;
		outline-offset: 0.25rem;
  }
}

Conclusión

Aunque no resuelve la encapsulación, es una herramienta muy útil para gestionar los estilos de tus aplicaciones.

Sin embargo, hay ciertas reglas sobre la revelancia de las capas que hay que tener en mente para evitar resultados inesperados, ya que al principio puede costar el cambio de mentalidad y depurar los estilos cuando existen capas anidadas.

Enlaces de interés

* La imagen del artículo es una foto de Clark Van Der Beken en Unsplash.

Relacionados

Charlamos sobre sistemas de diseño con Aarón García

A estas alturas nadie cuestiona ya los beneficios que tiene, de forma general, contar con un sistema de diseño (DS, Design System) para la creación de productos digitales. Para profundizar un poco más sobre este tema y descubrir cómo se están implementando en otros sitios, hemos tenido ocasión de charlar recientemente con Aarón García, Lead Design System Engineer en New Relic.

22/3/2023

Comentamos en equipo Tao of React de Alex Kondov

Como desarrolladores, una de las cosas a las que dedicamos mas tiempo es a leer, no solo código, sino también documentación, libros y, por supuesto, artículos sobre nuestro campo. Y no siempre es fácil encontrar buenos textos. Por si no lo conoces, te recomendamos uno de los artículos que más nos gustan en Redradix, TAO of React de Alex Kondov.

15/3/2023

Button Text