wandres.dev
CAPAS EN CASCADA · @layer y el control del orden

Por qué una capa vence a la especificidad

El criterio que faltaba en la cascada: qué problema resuelve @layer, en qué escalón actúa y por qué el CSS que está fuera de toda capa es el más fuerte de todos.

⏱ 17 min

Durante veinticinco años CSS no tuvo ninguna forma de decir “este bloque de reglas manda sobre aquel otro”. Tenía dos aproximaciones —el orden de los ficheros y el peso de los selectores— y las dos eran indirectas: expresaban autoridad como efecto secundario de otra cosa. @layer es el primer mecanismo que dice exactamente eso y nada más, y está situado en la cascada por encima de la especificidad, de modo que una sola clase en la capa correcta vence a un selector con tres identificadores en la capa anterior.

🎯 Al terminar esta lección sabrás
  • Situar el criterio de capas en el orden de la cascada y decir a qué vence.
  • Predecir el ganador entre dos declaraciones de capas distintas sin contar especificidad.
  • Explicar por qué el CSS sin capa es el más fuerte para declaraciones normales.
  • Distinguir lo que las capas resuelven de lo que no resuelven.

El criterio que faltaba

En el orden de la cascada, las capas ocupan el cuarto escalón, justo encima de la especificidad. La regla es de una línea: entre dos declaraciones normales de capas distintas, gana la de la capa más tardía en el orden de capas. Y por estar más arriba en la lista, esa comparación termina antes de que la especificidad llegue a mirarse.

Un ejemplo mínimo lo demuestra mejor que cualquier explicación:

@layer heredado, componentes;

@layer heredado {
  #app .boton.primario.grande { color: red; }   /* 1-3-0 */
}

@layer componentes {
  .boton { color: blue; }                        /* 0-1-0 */
}

Gana blue. Un selector con un identificador y tres clases pierde contra una sola clase, y no por poco: la especificidad no llega a compararse. La comparación acaba en el escalón de las capas, donde componentes está después de heredado.

Esa inversión de expectativas es todo el valor de la herramienta. Antes, “quiero que mi CSS gane al del tema heredado” solo se podía expresar escribiendo selectores más caros o poniendo !important, es decir, deformando el código para transmitir una decisión que no era sobre el código. Ahora se expresa directamente, en un sitio, y con una línea.

Queda por precisar qué significa exactamente “más tardía”, porque no es lo que la intuición sugiere. Las capas se ordenan por la primera vez que aparecen, y a partir de ahí ese orden es fijo para todo el documento. Volver a abrir una capa más adelante no la mueve: añade reglas a la posición que ya tenía.

@layer base {
  p { margin-block: 0.75em; }
}

@layer componentes {
  .aviso { border-inline-start: 3px solid; }
}

@layer base {                 /* no se mueve al final */
  h2 { text-wrap: balance; }
}

El orden sigue siendo base y luego componentes, porque base apareció primero. Esa acumulación es lo que permite repartir una misma capa entre muchos ficheros sin que el orden de carga la altere.

Y de ahí sale la práctica más importante de todas, que se explicará en detalle en la lección siguiente: declarar el orden completo de capas al principio, en un sitio único, con la forma de sentencia que no lleva contenido:

@layer reset, base, layout, componentes, utilidades;

A partir de esa línea, el orden está fijado con independencia de cuándo llegue cada fichero, en qué orden los coloque el empaquetador o qué fragmento cargue antes el usuario.

El CSS sin capa es el más fuerte

Aquí está la parte del diseño que más desconcierta y que hay que tener clara antes de tocar un proyecto real. Las declaraciones que no están dentro de ninguna capa se tratan, a efectos de este criterio, como si pertenecieran a una capa implícita situada al final del orden.

La consecuencia inmediata para declaraciones normales es que el CSS sin capas gana a todo el CSS que esté dentro de capas. No hay forma de declarar una capa que venza a lo no encapado; la capa implícita siempre va después.

@layer todo;

@layer todo {
  .boton { color: blue; }
}

/* Fuera de toda capa: gana esta, aunque esté escrita antes */
.boton { color: green; }

Es una decisión de diseño deliberada y razonable: garantiza que adoptar capas en un proyecto existente no puede romper nada, porque todo el CSS antiguo, que no está en ninguna capa, conserva su autoridad frente al nuevo. Pero tiene una implicación práctica que casi todo el mundo descubre por las malas.

Casi todo el mundo migra a capas al revés, y por eso concluye que no funcionan

El instinto al adoptar capas es empezar por lo nuevo: creas una capa componentes, metes ahí lo que escribes a partir de hoy y dejas el CSS viejo como estaba. Es lo razonable en cualquier otra migración y aquí es exactamente lo contrario de lo que hay que hacer. Todo el código antiguo, al no estar en ninguna capa, vive en la capa implícita final y por tanto le gana a tu capa nueva en todas las declaraciones normales. El efecto que observas es demoledor para la confianza en la herramienta: escribes CSS limpio dentro de una capa, no se aplica, y concluyes que las capas son un juguete que no sirve para código real. El movimiento correcto es el inverso: lo que se mete en una capa es lo viejo, y lo nuevo se escribe en capas posteriores o incluso fuera de toda capa mientras dure la transición. Envolver un fichero heredado completo en @layer heredado { ... }, o importarlo con layer(heredado), cuesta una línea y a partir de ese momento cualquier regla tuya de una clase le gana a cualquier selector suyo, por barroco que sea. La regla general que conviene grabarse es esta: encapar código lo debilita. Por eso se encapa aquello sobre lo que quieres tener la última palabra, no aquello con lo que quieres tenerla. Y por eso el orden de capas de un proyecto sano se lee de menos autoridad a más: reset primero, utilidades al final, y el CSS de terceros lo más al principio posible.

Lo que las capas no son

Tres confusiones frecuentes que conviene despejar antes de construir nada.

No son ámbito. Una capa no acota dónde se aplican sus reglas: un selector dentro de una capa alcanza exactamente a los mismos elementos que fuera de ella. Las capas cambian quién gana, no a quién apunta. Para acotar existe @scope, que es otra herramienta y otro criterio de la cascada.

No son encapsulación. Nada impide que una capa toque elementos de los que se ocupa otra. No hay privacidad, no hay colisiones evitadas, no hay renombrado. Si dos capas estilan lo mismo, se sigue produciendo un conflicto; lo único que cambia es que ahora el conflicto se resuelve por una decisión declarada en vez de por un accidente de orden.

No afectan a la herencia. Un valor heredado no pertenece a ninguna capa y no compite con nada. Las capas actúan solo sobre declaraciones que compiten para el mismo elemento y la misma propiedad.

Hay además una asimetría que ya viste en el nivel de la cascada y que conviene recordar aquí porque es la fuente de las sorpresas restantes: para declaraciones importantes el orden de capas se invierte, y gana la capa más temprana. Un !important en la capa reset vence a un !important en la capa utilidades. Y como el CSS sin capa vive en la capa implícita final, un !important fuera de toda capa es el más débil de todos los importantes.

Cuándo compensa

No todo proyecto necesita capas, y conviene tener el criterio claro. Merecen la pena cuando se cumple al menos una de estas tres condiciones.

Hay CSS que no controlas cargándose junto al tuyo: una biblioteca de componentes, un tema, un widget de un proveedor, un editor de contenido. Aquí la ganancia es inmediata y enorme.

Hay más de un equipo escribiendo en la misma hoja de estilos. Las capas convierten la jerarquía de autoridad en algo escrito y revisable en lugar de en una convención oral sobre el orden de los ficheros.

El CSS se carga en fragmentos cuyo orden depende de la navegación. Sin capas, quién gana es no determinista; con capas, deja de depender del orden de llegada.

Si no se cumple ninguna —un proyecto pequeño, de una persona, con una hoja de estilos que se carga entera— las capas siguen siendo útiles como documentación de la intención, pero no resuelven ningún problema que tengas. La sintaxis completa, con sus formas y sus reglas de orden, es la lección siguiente.