La cascada: seis criterios y solo uno es la especificidad
El algoritmo completo que convierte muchas declaraciones en un único valor, con sus criterios en el orden real de la especificación y el lugar exacto que ocupa la especificidad.
Pregunta a diez desarrolladores cómo decide CSS qué regla gana y nueve empezarán por la especificidad. Es el error de modelo más extendido de todo el frontend, y tiene consecuencias caras: gente contando ceros y unos durante veinte minutos cuando el resultado ya estaba decidido dos criterios antes, en un escalón que ni siquiera habían mirado. La cascada es un algoritmo de ordenación con seis criterios encadenados, la especificidad es el quinto, y solo se consulta cuando los cuatro anteriores han quedado empatados.
- Enumerar los criterios de la cascada en el orden exacto en que se aplican.
- Explicar qué declaraciones entran siquiera en la ordenación y cuáles se filtran antes.
- Justificar por qué la especificidad ocupa una posición tan baja en la lista.
- Distinguir el trabajo de la cascada del de la herencia.
De muchas declaraciones a un único valor
El planteamiento formal es sencillo y conviene tenerlo exacto. Para cada propiedad de cada elemento, el motor reúne todas las declaraciones que le aplican, las ordena por precedencia y se queda con la primera. El resultado se llama valor cascadeado. La cascada opera propiedad a propiedad: no hay ganadores globales, no gana “una regla”. De una misma regla puede ganar el color y perder el background.
Antes de ordenar nada hay un filtrado. Una declaración entra en la lista solo si se cumplen tres condiciones: pertenece a una hoja de estilos que se está aplicando a este documento y a este medio, está dentro de reglas condicionales cuyas condiciones son ciertas, y el selector de su regla casa con el elemento. Todo lo que no pase el filtro no compite: no es que pierda, es que no está.
Lo que queda son declaraciones candidatas, potencialmente decenas para una misma propiedad, y ahí entra el algoritmo.
flowchart TB cand[Declaraciones candidatas para una propiedad] --> c1[1 Origen e importancia] c1 --> c2[2 Contexto de encapsulacion] c2 --> c3[3 Atributo style] c3 --> c4[4 Capas en cascada] c4 --> c5[5 Especificidad] c5 --> c6[6 Orden de aparicion] c6 --> val[Valor cascadeado unico] c1 -.->|si hay desempate| c2 style cand fill:#89b4fa,color:#11111b style c1 fill:#f38ba8,color:#11111b style c2 fill:#94e2d5,color:#11111b style c3 fill:#fab387,color:#11111b style c4 fill:#cba6f7,color:#11111b style c5 fill:#f9e2af,color:#11111b style c6 fill:#89b4fa,color:#11111b style val fill:#a6e3a1,color:#11111b
Cada escalón solo se consulta si el anterior no ha resuelto el empate. Si dos declaraciones vienen de orígenes distintos, la comparación termina en el primer criterio y ni la especificidad ni el orden llegan a mirarse.
Los seis criterios, en el orden de la especificación
| # | Criterio | Qué compara | Dónde se estudia |
|---|---|---|---|
| 1 | Origen e importancia | De quién viene la declaración y si lleva !important |
Este nivel |
| 2 | Contexto de encapsulación | Árbol de sombra frente a documento externo | Este nivel |
| 3 | Atributo style |
Declaración pegada al elemento frente a regla con selector | Este nivel |
| 4 | Capas en cascada | Qué capa es más tardía en el orden de capas | Nivel 4 |
| 5 | Especificidad | El peso del selector | Nivel 3 |
| 6 | Orden de aparición | La última declaración del documento gana | Este nivel |
Tres observaciones sobre la tabla, porque las tres se repiten mal en internet.
La primera: !important no es un criterio aparte. Forma parte del primero, junto al origen. Eso explica por qué su efecto parece desproporcionado: no salta la especificidad, salta directamente al escalón superior de todos.
La segunda: las capas van por encima de la especificidad. Una declaración de una capa posterior vence a una declaración de una capa anterior aunque el selector de la segunda sea infinitamente más específico. Un selector con tres identificadores dentro de una capa temprana pierde contra una sola clase escrita en una capa posterior. Esa es toda la razón de existir de @layer y la estudiarás con detalle en su nivel.
La tercera: hay un criterio más reciente. El borrador del nivel 6 del módulo de cascada añade la proximidad de ámbito, que se aplica a las reglas dentro de un @scope y compara cuántos saltos hay entre la raíz de ámbito y el elemento. Su posición en la lista es entre la especificidad y el orden de aparición: se consulta después de que la especificidad haya empatado. Es el único criterio que se ha añadido al algoritmo en años, y conviene saber que existe para no describir la lista como cerrada.
Por qué la especificidad está tan abajo
No es un accidente ni una decisión discutible: la posición de cada criterio codifica una jerarquía de autoridad, ordenada de la más institucional a la más incidental.
El primer criterio decide entre actores: el fabricante del navegador, la persona que lee y el autor del sitio. Es una cuestión de legitimidad, y ninguna consideración técnica sobre selectores debería poder anularla. Si un usuario con baja visión ha impuesto un tamaño mínimo de texto, ningún selector por elaborado que sea puede pasar por encima.
El segundo decide entre fronteras de encapsulación: lo que está dentro de un componente con árbol de sombra frente a lo que está fuera. Para las declaraciones normales gana el contexto exterior, de modo que un componente puede fijar valores por defecto que la página sobrescribe con facilidad; para las importantes gana el interior, de modo que un componente puede imponer lo que no admite discusión. Es una decisión de diseño de componentes, no de peso de selectores.
El tercero decide entre lo pegado al elemento y lo que llega por selector. Un style en el HTML es una afirmación sobre ese elemento concreto, y por eso vence a cualquier regla de hoja de estilos de la misma importancia.
El cuarto decide entre decisiones de arquitectura explícitas: qué bloque de CSS ha declarado el equipo que manda sobre cuál.
Solo cuando todo eso empata llegamos al quinto, que es una heurística sobre lo concreto que es un selector. Y es exactamente eso, una heurística: la idea de que un selector más detallado expresa una intención más específica y por tanto debería ganar. Funciona razonablemente, pero es la aproximación más burda de las seis, porque mide la forma del selector y no la intención de quien lo escribió.
El sexto, el orden de aparición, es un desempate final que garantiza que el algoritmo siempre termina con un ganador. No hay empates irresolubles en CSS.
Aquí está el diagnóstico que reordena quince años de prácticas. Durante toda la era anterior a las capas, la comunidad tenía un único mecanismo para expresar autoridad entre bloques de CSS —“mi reset debe perder siempre contra mis componentes”, “el CSS del plugin no debe ganarle al del tema”— y ese mecanismo era la especificidad, que no está hecha para eso. La especificidad mide cuán detallado es un selector; nunca midió quién manda. Usarla como sustituto produjo exactamente lo que cabía esperar de una métrica mal aplicada: la carrera armamentística. Cada capa de código que quería ganar añadía un nivel más al selector, el siguiente que llegaba tenía que superarlo, y como el crecimiento no tiene tope se acababa en !important, que sí es un techo pero destruye toda posibilidad de sobrescritura posterior. El resultado no era CSS mal escrito por gente descuidada: era la consecuencia inevitable de codificar autoridad en una métrica de forma. Lo que cambió @layer es que ahora hay un criterio explícito, declarado arriba del todo y por encima de la especificidad, para decir quién manda. Con él la especificidad vuelve a su función original, que es desempatar entre reglas de la misma capa, y ahí sí funciona bien. La consecuencia inmediata para tu forma de trabajar: en cuanto sientas la tentación de subir la especificidad de un selector para ganar una pelea, párate, porque el criterio correcto para esa pelea está un escalón más arriba y no lo estás usando.
Cascada no es herencia
Son dos mecanismos distintos que se ejecutan en momentos distintos y resuelven preguntas distintas, y confundirlos produce diagnósticos imposibles.
La cascada actúa cuando hay varias declaraciones compitiendo para una propiedad de un elemento y elige una. Si no hay ninguna declaración, la cascada no produce nada: no falla, simplemente no hay valor cascadeado.
La herencia actúa precisamente en ese hueco. Cuando la cascada no ha producido valor para una propiedad, el motor mira si esa propiedad es de las que se heredan; si lo es, toma el valor calculado del padre, y si no lo es, toma el valor inicial de la propiedad. La herencia no compite con nada y no participa en la ordenación: es lo que ocurre cuando la ordenación se queda vacía.
De esta separación sale una consecuencia que sorprende y que conviene fijar ahora: un valor heredado siempre pierde contra cualquier declaración, por débil que sea. Si el padre tiene color: red heredable y el hijo tiene una regla con especificidad mínima que dice color: inherit o cualquier otro color, gana la regla del hijo. La herencia no tiene especificidad porque no está en la carrera. Por eso * { color: inherit } es una declaración destructiva: convierte la herencia, que solo actuaba en ausencia de reglas, en una declaración explícita sobre todos los elementos que gana a cualquier valor heredado de forma natural.
El detalle completo de la herencia, con sus cuatro palabras clave globales y la cadena de valores, es el nivel 5. Aquí basta con la frontera: la cascada elige entre lo que hay; la herencia rellena cuando no hay nada.