Contexto, atributo style y orden de aparición
Los tres criterios que rodean a la especificidad y a las capas: la frontera del árbol de sombra, los estilos pegados al elemento y la definición exacta de qué significa venir después.
Entre el origen y las capas hay dos criterios que casi ningún artículo menciona, y al final de todo hay uno que se menciona mal. El del contexto de encapsulación decide qué pasa en la frontera de un componente con árbol de sombra. El del atributo style decide entre lo pegado al elemento y lo que llega por selector. Y el orden de aparición, que parece trivial, es el criterio que más bugs de producción provoca, porque quien lo controla de verdad no eres tú: es tu herramienta de construcción.
- Predecir quién gana entre un estilo del documento y uno del interior de un árbol de sombra.
- Explicar qué atraviesa la frontera de encapsulación y qué no.
- Definir con precisión qué es el orden del documento para hojas enlazadas e importadas.
- Reconocer los fallos que produce un orden de carga no determinista y cómo se blindan.
El contexto de encapsulación
Cuando un componente usa un árbol de sombra, sus estilos internos y los del documento que lo contiene pertenecen a contextos de encapsulación distintos, y la cascada los compara con una regla propia situada en el segundo escalón, justo debajo del origen.
La regla tiene dos mitades simétricas a las que ya estás acostumbrado. Para declaraciones normales gana el contexto exterior: lo que escribe la página vence a lo que escribe el componente. Para declaraciones importantes gana el interior: lo que el componente marca como innegociable vence a la página.
El diseño resultante es exactamente el que quieres para una biblioteca de componentes. Todo lo que el componente considere un valor por defecto lo escribe normal, y la página lo sobrescribe sin esfuerzo ni escalada de selectores. Todo lo que el componente considere estructural —lo que si se toca lo rompe— lo escribe importante, y entonces la página no puede estropearlo por accidente.
Conviene distinguir esto de lo que atraviesa la frontera, que es otra pregunta y se responde con la herencia, no con la cascada. Los selectores del documento no alcanzan al interior del árbol de sombra: ese es todo el sentido de la encapsulación. Pero las propiedades heredables sí bajan con normalidad, porque la herencia opera sobre el árbol aplanado y no le importan las fronteras. Y las custom properties son heredables, de modo que atraviesan igual.
De ahí sale el patrón canónico para hacer configurable un componente encapsulado: no expongas selectores, expón variables.
/* Dentro del componente */
:host {
--relleno: 0.75rem 1rem;
--fondo: oklch(96% 0.01 250);
}
.boton {
padding: var(--relleno);
background: var(--fondo);
}
/* Desde la página, sin romper la encapsulación */
mi-boton { --fondo: oklch(60% 0.15 250); }
La variable se define fuera, se hereda hacia dentro y el componente la lee. No hace falta que el documento pueda seleccionar .boton, y de hecho es mejor que no pueda: la superficie de configuración queda explícita y documentada, en vez de ser cualquier clase interna que alguien descubra inspeccionando.
El atributo style
El tercer criterio compara declaraciones pegadas directamente al elemento con declaraciones que llegan por un selector. Las primeras ganan, y la comparación se hace por separado para normales y para importantes: una normal en línea vence a una normal por selector, y una importante en línea vence a una importante por selector.
Esto no es especificidad. Durante años se enseñó que el atributo style “vale mil” en la cuenta de especificidad, como si fuera un cuarto dígito. Nunca fue una buena descripción y hoy es directamente falsa: es un criterio anterior a la especificidad, no un valor dentro de ella. La diferencia importa porque explica por qué ningún selector, por elaborado que sea, gana a un estilo en línea de la misma importancia. No estás perdiendo por poco; estás perdiendo en otro escalón.
La consecuencia arquitectónica es que el atributo style es un recurso caro. Todo lo que se escriba ahí queda fuera del alcance de tus hojas de estilo y de cualquier tematización. La excepción razonable, y hoy muy extendida, es usarlo para pasar datos, no apariencia: escribir custom properties desde JavaScript o desde la plantilla del servidor, y dejar que el CSS decida qué hacer con ellas.
<div class="tarjeta" style="--indice: 3">…</div>
.tarjeta {
animation-delay: calc(var(--indice) * 60ms);
}
Así lo único que vive en el marcado es un número que cambia por fila, y toda la apariencia sigue estando en la hoja de estilos, donde se puede sobrescribir, tematizar y capar.
Orden de aparición: qué significa “después”
El último criterio dice que gana la última declaración en orden del documento. Suena trivial y no lo es, porque “orden del documento” tiene una definición precisa con cuatro cláusulas.
Las hojas se ordenan en el orden en que quedan finalmente asociadas al documento. Las declaraciones de una hoja importada con @import se ordenan como si el contenido de la hoja importada estuviera pegado en el lugar de la regla @import. Las hojas enlazadas de forma independiente se ordenan por el orden de enlazado que fije el lenguaje del documento, que en HTML es el orden de aparición de las etiquetas. Y las declaraciones de atributos style se ordenan según la posición del elemento en el documento, después de todas las hojas de estilo.
De esas cuatro cláusulas salen tres realidades cotidianas.
La primera: el orden de tus etiquetas link es una decisión de arquitectura, no de fontanería. Mover un fichero tres líneas más arriba en el HTML puede cambiar qué gana en cientos de elementos.
La segunda: @import no crea un ámbito ni una prioridad. Lo importado se comporta como si estuviera escrito ahí mismo. Un @import al principio de tu hoja principal coloca todo ese CSS antes que el tuyo, que es justo lo que quieres para un reset y justo lo que no quieres para una sobrescritura.
La tercera: una hoja insertada en tiempo de ejecución se coloca al final, y por tanto gana a todo lo demás con la misma especificidad. Es la razón por la que un estilo inyectado por una extensión, por un widget de terceros o por un componente que crea su propia etiqueta style al montarse suele ganar sin que tú hayas hecho nada mal.
Aquí está la clase de bug de producción más frustrante que existe en CSS, y su causa está entera en este criterio. En una aplicación con división de código, el CSS de cada fragmento se carga cuando se carga el fragmento, y por tanto el orden de aparición depende del orden de navegación del usuario. Si el componente A y el componente B tienen reglas que compiten con la misma especificidad, quien gane dependerá de cuál se cargó primero, y eso cambia según por dónde entró la persona al sitio. El resultado es el bug perfecto: no se reproduce en desarrollo, porque en desarrollo todo se carga junto y en un orden fijo; no se reproduce recargando, porque al recargar ya está todo en memoria; y solo aparece siguiendo una ruta concreta de navegación que nadie apunta en el informe. Puedes pasarte días persiguiéndolo con la certeza de que el CSS es correcto, y lo es: lo que no es determinista es el orden. La cura estructural es una y solo una: dejar de depender del orden de aparición para decidir quién gana, declarando el orden de capas una sola vez, al principio, en una hoja que siempre se carga la primera. Una declaración @layer reset, base, componentes, utilidades; fija el orden de autoridad de forma global e inmutable, y a partir de ahí da exactamente igual en qué momento llegue cada fragmento de CSS: cada regla cae en su capa y la capa manda. Es el argumento más fuerte para adoptar capas en una aplicación moderna, muy por encima de la comodidad de organizar ficheros, y curiosamente es el que menos se menciona.
Proximidad de ámbito, el criterio añadido
Falta mencionar un criterio que no estaba en la lista clásica y que aparece en el borrador del nivel 6 del módulo de cascada, ligado a @scope. Cuando dos declaraciones vienen de reglas con raíces de ámbito distintas, gana la que tenga menos saltos entre su raíz de ámbito y el elemento afectado. Una regla sin raíz de ámbito cuenta como distancia infinita, es decir, pierde frente a cualquier regla acotada.
Su posición en la ordenación conviene tenerla exacta porque se cuenta mal a menudo: se evalúa después de la especificidad y antes del orden de aparición. Es decir, primero se comparan los pesos de los selectores como siempre, y solo si empatan se mira la proximidad.
El caso que motiva el criterio es el de temas anidados: una zona clara dentro de una zona oscura dentro de una página clara. Sin proximidad, quién gana dependería del orden en que se escribieron los temas, que es una respuesta arbitraria; con proximidad, gana el ámbito más cercano al elemento, que es siempre la respuesta que el diseñador esperaba.
Con esto queda descrito el algoritmo completo. Lo que falta del nivel es aprender a verlo funcionar sobre un caso real, que es el asunto de la última lección.