Capas anidadas, subcapas implícitas y capas anónimas
Cómo se anidan las capas y se nombran con puntos, el orden completo que resulta cuando hay subcapas, y qué hace exactamente una capa sin nombre.
Las capas se pueden meter unas dentro de otras, y ahí aparece una estructura de orden que reproduce a pequeña escala la del nivel superior, incluida la regla de que lo que no está en ninguna subcapa va al final. Entender ese orden completo es lo que permite que una biblioteca ofrezca capas internas configurables sin que colisionen con las tuyas, y evita el desconcierto de ver ganar a una regla que está escrita antes que su rival.
- Declarar capas anidadas con las dos sintaxis disponibles y saber que son equivalentes.
- Ordenar correctamente un conjunto de capas con subcapas y reglas sueltas.
- Explicar el papel de la subcapa implícita dentro de cada capa.
- Decidir cuándo una capa anónima es la elección correcta y cuándo es un problema.
Anidar capas
Una capa declarada dentro de otra es una subcapa, y su nombre completo concatena los dos segmentos. Hay dos formas de escribirlo y significan exactamente lo mismo:
/* Anidando bloques */
@layer marco {
@layer tema {
blockquote { color: rebeccapurple; }
}
}
/* Con notación de punto */
@layer marco.tema {
blockquote { color: rebeccapurple; }
}
La notación de punto es especialmente útil para añadir reglas a una subcapa que declaró otro: si importas una biblioteca a la capa marco y esa biblioteca declara internamente @layer tema, puedes escribir en marco.tema desde tu propio fichero sin tocar el suyo.
Hay una restricción importante: una capa anidada no puede escapar de su padre. Dentro de @layer marco { ... } no hay forma de referirse a una capa de nivel superior; todo lo que declares ahí dentro será una subcapa de marco. Eso es lo que hace fiable el aislamiento de nombres al importar bibliotecas: sus capas internas nunca podrán interferir con las tuyas de nivel superior, aunque se llamen igual.
Un detalle útil para depurar: en el modelo de objetos, el nombre que expone una regla de capa anidada es relativo, no absoluto. Una capa tema declarada dentro de marco se reporta como tema, no como marco.tema. Si estás recorriendo las reglas desde la consola para reconstruir el orden, tienes que ir componiendo los nombres a medida que desciendes.
El orden completo con subcapas
Las reglas de ordenación son tres y se aplican recursivamente.
Las capas de nivel superior se ordenan por primera aparición. Dentro de cada capa, las subcapas se ordenan también por primera aparición. Y en los dos niveles, lo que no está en ninguna subcapa se coloca al final, en una subcapa implícita que viene después de todas las explícitas.
Este ejemplo, tomado del que usa la propia especificación, lo muestra completo:
/* Sin capa: va al final del todo */
h1 { color: darkslateblue; }
@layer reset.tipografia {
strong { font-weight: bold; }
}
@layer marco {
.titulo { font-weight: 100; }
@layer tema {
h1, h2 { color: maroon; }
}
}
@layer reset {
[hidden] { display: none; }
}
El orden resultante, de menor a mayor autoridad para declaraciones normales, es:
reset.tipografiareset, subcapa implícitamarco.temamarco, subcapa implícita- Capa implícita exterior, donde vive la regla del
h1
Fíjate en dos cosas que no son obvias. La primera: reset aparece antes que marco aunque su bloque esté escrito después, porque su primera aparición fue la línea de @layer reset.tipografia. La segunda: dentro de marco, la regla de .titulo gana a las de marco.tema aunque esté escrita antes, porque las reglas sueltas de una capa van a su subcapa implícita, que es la última.
Esta es la simetría que hace que el sistema sea coherente y la que más desconcierta cuando la encuentras sin esperarla. Ya sabes que a nivel global el CSS sin capa gana a todo el CSS encapado, porque va a una capa implícita final. Pues bien: exactamente lo mismo ocurre un nivel más abajo. Dentro de @layer marco, las reglas que no metas en ninguna subcapa se agrupan en una subcapa implícita colocada detrás de todas las explícitas, y por tanto ganan a las de cualquier subcapa por muy tarde que estas se declaren. El resultado práctico es contraintuitivo hasta que lo interiorizas: si abres una capa, escribes unas reglas sueltas y luego declaras dentro una subcapa overrides con la intención de que sobrescriba a las anteriores, conseguirás lo contrario, porque tus reglas sueltas se han ido al final y tus “overrides” se han quedado delante. La consecuencia de diseño es una recomendación tajante: dentro de una capa que vaya a tener subcapas, no dejes reglas sueltas. O todo está en subcapas o nada lo está. Mezclar produce un orden que hay que reconstruir mentalmente cada vez y que ningún lector casual del fichero va a adivinar. Y una consecuencia adicional para quien publica una biblioteca: si la envuelves entera en @layer mibiblioteca { ... } y dentro organizas en subcapas, deja el nivel de mibiblioteca completamente vacío de reglas sueltas, porque cualquiera que importe tu hoja a una capa suya heredará esa estructura y las reglas sueltas se le colarán con la máxima autoridad dentro de ese subárbol.
Capas anónimas
Un bloque @layer sin nombre crea una capa que no se puede referenciar desde ningún sitio:
@layer {
.widget { border: 1px solid; }
}
Cada aparición de un bloque anónimo es una capa distinta. Estos dos bloques no comparten nada:
@layer { /* capa uno */ }
@layer { /* capa dos, sin relación con la anterior */ }
Dentro de una misma capa anónima, en cambio, las subcapas con el mismo nombre sí son la misma capa, porque comparten el segmento anónimo del padre:
@layer {
@layer foo { /* subcapa foo de esta capa anónima */ }
@layer foo { /* la misma subcapa foo */ }
}
La forma de importación con la palabra clave a secas produce lo mismo: @import url("x.css") layer; mete la hoja en una capa anónima.
Su ventaja es la garantía de aislamiento: nadie puede añadir reglas a esa capa, ni por descuido ni a propósito. Su desventaja es la simétrica y hay que sopesarla: una capa anónima no puede aparecer en la sentencia de orden, así que su posición la fija el punto donde aparece, y eso reintroduce por la puerta de atrás la dependencia del orden de carga que las capas venían a eliminar.
La regla práctica que sale de ahí es sencilla: usa capas anónimas solo cuando el fichero que las contiene se carga en una posición conocida y fija, típicamente porque forma parte de tu paquete base. Para cualquier cosa cuyo momento de carga dependa de la navegación, ponle nombre y decláralo en la sentencia.
Cuándo anidar
Anidar capas no es gratis en complejidad, así que conviene tener criterio.
Sí, para encerrar código ajeno. Importar cada dependencia a su propia capa y dejar que sus capas internas se aniden dentro es el uso que justifica la característica.
Sí, para una biblioteca propia con partes de distinta autoridad. Si publicas un sistema de componentes, ofrecer sistema.reset, sistema.base y sistema.componentes permite a quien lo consuma insertar su código entre tus subcapas, que es una forma de extensibilidad muy limpia y que no le obliga a pelear con tus selectores.
No, para reflejar la estructura de carpetas. Una jerarquía de capas que reproduce el árbol de ficheros no aporta autoridad: aporta ruido. Las capas describen quién manda, y las carpetas describen dónde está el código; son ejes distintos y forzarlos a coincidir complica los dos.
No, para simular ámbito. Ya se dijo y conviene repetirlo porque la anidación lo sugiere visualmente: una subcapa no acota el alcance de sus selectores en absoluto. Sigue siendo CSS global.