Declarar capas y fijar su orden
Las dos formas de la regla @layer, por qué la sentencia de orden va la primera de todo, cómo se nombran las capas y qué puede alterar el orden sin que lo notes.
La sintaxis de @layer cabe en dos formas y la segunda es la que de verdad importa: una lista de nombres sin contenido que fija de una vez el orden de autoridad de todo el proyecto. Escribirla al principio no es una convención estética, es lo que convierte el sistema en determinista, y omitirla deja el orden a merced del orden de carga, que es exactamente el problema del que querías salir.
- Usar las dos formas de
@layery saber cuándo toca cada una. - Explicar por qué la sentencia de orden debe preceder a cualquier contenido.
- Aplicar las reglas de nombrado, incluidas las restricciones de la especificación.
- Detectar situaciones en las que el orden de capas deja de ser determinista.
Las dos formas
La forma de bloque asigna reglas a una capa:
@layer componentes {
.boton {
padding-inline: 1rem;
border-radius: 0.5rem;
}
}
Puede reabrirse tantas veces como quieras, desde tantos ficheros como quieras, y todo lo que caiga dentro se acumula en la misma capa:
/* botones.css */
@layer componentes {
.boton { padding-inline: 1rem; }
}
/* tarjetas.css */
@layer componentes {
.tarjeta { padding: 1.25rem; }
}
La forma de sentencia declara nombres sin asignar nada:
@layer reset, base, layout, componentes, utilidades;
No contiene reglas y su único efecto es crear las capas y fijar su orden relativo. A partir de esa línea, utilidades vence a componentes, que vence a layout, y así hacia atrás, con independencia de dónde y cuándo aparezca el contenido de cada una.
Por qué la sentencia va primero
Las capas se ordenan por su primera aparición. Si no declaras el orden por adelantado, la primera aparición de cada capa es el punto donde por casualidad se cargó el fichero que la abre, y ese punto depende de cosas que no controlas: el orden de las etiquetas de enlace, el orden en que el empaquetador concatena los módulos, o el fragmento de código que se descargó primero según por dónde navegó el usuario.
Con la sentencia al principio, todo eso deja de importar. El orden queda fijado antes de que llegue nada de contenido, y cada bloque de reglas cae en su sitio.
/* orden.css — se carga siempre el primero */
@layer reset, terceros, base, layout, componentes, utilidades;
Un detalle de sintaxis que conviene conocer porque parece un error y no lo es: la sentencia @layer puede aparecer antes de las reglas @import sin invalidarlas. @import exige estar al principio de la hoja, pero la especificación admite explícitamente que le precedan sentencias de capa. Esto es lo que permite fijar el orden y luego importar en las capas correspondientes:
@layer reset, terceros, base, componentes;
@import url("normalize.css") layer(reset);
@import url("tema-proveedor.css") layer(terceros);
@layer base {
:root { --espaciado: 1rem; }
}
Sin esa concesión habría un problema de huevo y gallina: no podrías declarar el orden antes de importar, y los importados fijarían el orden por su cuenta.
Nombrar capas
Los nombres son identificadores CSS, con las reglas habituales: letras, dígitos, guiones y guiones bajos, sin empezar por dígito, y sensibles a mayúsculas y minúsculas. @layer Base y @layer base son dos capas distintas, lo que produce errores silenciosos difíciles de ver en una revisión. Fija una convención y cíñete a ella.
Las palabras clave globales del lenguaje están reservadas y no se pueden usar como nombre de capa: initial, inherit, unset, revert y revert-layer. Usarlas invalida la regla en tiempo de análisis, y como los errores de CSS son silenciosos, el síntoma será que esa capa sencillamente no existe.
Sobre convenciones, tres criterios que se sostienen en proyectos grandes.
Nombra por responsabilidad, no por fichero. componentes describe qué hace la capa; boton-y-tarjeta describe qué había dentro el día que se escribió.
Pocas capas y estables. Entre cuatro y siete cubre casi cualquier proyecto. Una capa por componente convierte el orden en una lista imposible de razonar y no aporta nada, porque los componentes rara vez compiten entre sí.
El orden es documentación. La línea de la sentencia debería poder leerse como una frase: primero el reset, luego lo de terceros, luego mis bases, luego el layout, luego los componentes, y al final las utilidades. Si al leerla en voz alta no suena a jerarquía de autoridad, es que los nombres están mal elegidos.
Hay una trampa que la propia especificación se molesta en documentar con un ejemplo, y que en producción produce bugs imposibles de creer. Las capas declaradas dentro de una regla condicional solo contribuyen al orden si la condición se cumple. Como el orden de capas es global al documento y se fija por primera aparición, eso significa que el orden puede cambiar según la ventana o según las preferencias del usuario:
@media (min-width: 30em) {
@layer layout { .titulo { font-size: x-large; } }
}
@media (prefers-color-scheme: dark) {
@layer tema { .titulo { color: white; } }
}
@layer tema, layout;Si la primera consulta se cumple, la capa layout aparece por primera vez ahí y queda antes que tema. Si no se cumple pero sí la segunda, o si no se cumple ninguna, el orden lo fija la sentencia final y queda tema antes que layout. Es decir: el mismo CSS resuelve ganadores distintos según el ancho de la ventana, y no por una media query de estilo sino por una reordenación de la jerarquía de autoridad. Ahora imagina el informe de error: “en pantallas grandes el color del título es el correcto, en pequeñas no, pero la regla del color no está dentro de ninguna media query”. Puedes mirar el CSS del color durante horas sin encontrar nada, porque el CSS del color es correcto; lo que cambió fue quién manda. La prevención es de una sola línea y es la misma de siempre: declara todas las capas por adelantado, en una sentencia, fuera de cualquier regla condicional. Con la sentencia arriba del todo, las apariciones posteriores dentro de consultas ya no fijan nada porque las capas existen desde antes, y el orden pasa a ser inmune a la ventana, a las preferencias y al empaquetador.
Comprobar el orden real
Dos maneras de verificarlo sin conjeturas.
La primera, desde el inspector: los navegadores actuales muestran a qué capa pertenece cada regla en el panel de estilos, de forma que en un conflicto puedes leer directamente si la comparación se resolvió por capa.
La segunda, desde la consola, recorriendo las reglas de las hojas y quedándote con las de capa. Las reglas de bloque de capa exponen su nombre, y las de sentencia exponen la lista de nombres que declaran:
const capas = [];
for (const hoja of document.styleSheets) {
let reglas;
try { reglas = hoja.cssRules; } catch { continue; }
for (const r of reglas) {
if (r.name) capas.push(r.name); // bloque de capa
else if (r.nameList) capas.push(...r.nameList); // sentencia de capa
}
}
console.log(capas);
El resultado te da las capas en el orden en que el navegador las encontró, que es el orden real que está usando. Si esa lista no coincide con la que escribiste en tu sentencia, tienes una capa declarándose antes de tiempo en algún sitio, y casi siempre está dentro de una regla condicional o dentro de un fichero de terceros.