concepto CSS vigente

Especificidad

Fundamentos de CSS

La especificidad es el peso de un selector CSS que determina cuál declaración gana cuando dos reglas del mismo origen compiten por la misma propiedad; se calcula sumando puntos según el tipo de selector: inline style, IDs, clases/atributos/pseudo-clases, y tipos/pseudo-elementos.

La especificidad es el sistema de pesos con el que CSS decide qué declaración gana cuando dos o más reglas del mismo origen apuntan al mismo elemento y definen la misma propiedad. Es el segundo factor de la cascada — se evalúa solo cuando el origen de las reglas es el mismo (generalmente, todo el CSS viene del autor) y determina el ganador antes de recurrir al orden de aparición.

Entender la especificidad es entender por qué “poner más CSS” a veces no funciona. Cuando una regla no tiene efecto aunque parece correcta, la causa más frecuente es que otra regla con mayor especificidad ya definió esa propiedad para ese elemento. No es un bug del navegador ni del CSS — es el sistema funcionando como fue diseñado. Pero sin conocer las reglas de cálculo, el resultado parece arbitrario.

La especificidad se calcula como una suma de puntos agrupados en cuatro categorías, evaluadas de mayor a menor peso. No es un número decimal — es una tupla de cuatro valores que se comparan de izquierda a derecha: (A, B, C, D). Gana el selector con el número mayor en la categoría de más peso; si empatan, se compara la siguiente. Solo si empatan las cuatro categorías se recurre al orden de aparición.

selector A B C D inline ID clase tipo p 0 0 0 1 .destacado p 0 0 1 1 #header .nav p 0 1 1 1 ganador: #header .nav p — tiene ID (B=1), los otros tienen B=0 el desempate se hace de izquierda a derecha (A→B→C→D)

Cómo funciona

Las cuatro categorías (A, B, C, D)

La especificidad se calcula sumando puntos en cuatro categorías:

A — Style inline (style=""): vale (1,0,0,0). Un atributo style="" en el HTML siempre supera a cualquier regla de hoja de estilo, sin importar cuántos IDs o clases tenga ese selector.

B — ID (#id): cada selector de ID suma (0,1,0,0). Por eso los IDs son tan difíciles de sobreescribir: incluso una cadena larga de clases no alcanza la especificidad de un solo #id.

C — Clase, atributo y pseudo-clase: cada selector de clase (.clase), selector de atributo ([attr]) y pseudo-clase (:hover, :nth-child()) suma (0,0,1,0).

D — Tipo de elemento y pseudo-elemento: cada selector de tipo (p, div, h1) y pseudo-elemento (::before, ::first-line) suma (0,0,0,1).

El selector universal (*) y los combinadores (espacio, >, +, ~) no suman especificidad (valen 0,0,0,0).

p                        /* (0,0,0,1) */
.clase                   /* (0,0,1,0) */
#id                      /* (0,1,0,0) */
style=""                 /* (1,0,0,0) */

p.clase                  /* (0,0,1,1) — tipo + clase */
#nav .link               /* (0,1,1,0) — ID + clase */
#nav .link:hover         /* (0,1,2,0) — ID + clase + pseudo-clase */
div#nav p.link::before   /* (0,1,1,2) — ID + clase + tipo + tipo + pseudo-elemento */

Cómo se comparan

Los cuatro valores se comparan de izquierda a derecha. En cuanto un par de valores difiere, el mayor gana y los siguientes no se evalúan:

  • (0,1,0,0) gana a (0,0,99,99) — aunque el segundo tiene muchas más clases y tipos, el B del primero (1) supera al B del segundo (0) y eso termina el desempate.
  • (0,0,2,0) gana a (0,0,1,99) — dos clases ganan a una clase más muchos tipos.
  • (0,0,1,1) y (0,0,1,0) → empatan en A, B y C, desempatan en D: gana (0,0,1,1).

!important — la excepción que rompe el sistema

La declaración !important extrae esa declaración de la competencia de especificidad normal y la coloca en una capa de mayor prioridad dentro de la cascada. Una declaración !important gana sobre cualquier declaración normal, sin importar su especificidad.

p { color: red !important; }  /* gana sobre cualquier otra color sin !important */
#id { color: blue; }          /* pierde contra red !important */

Si dos declaraciones son !important, entonces sí se compara su especificidad — pero entre las !important. Y si eso también empata, gana la que aparece antes (al revés del orden normal: dentro de !important, gana la más temprana entre las del mismo origen).

Por qué evitar !important

!important no es una herramienta de debug — es un parche. Cuando se usa para “forzar” un estilo, invariablemente llega el momento en que necesitás sobreescribir ese !important, y la única forma es otro !important con mayor especificidad. Eso crea una carrera de !important que hace el CSS imposible de mantener. Los casos legítimos son muy acotados: accesibilidad (estilos de usuario que deben sobrevivir al CSS del autor), overrides de librerías de terceros que usaron mala especificidad, o utilidades de un sistema de diseño donde el !important es intencional y documentado.

Gotchas conceptuales

  • No existe “999 clases supera a un ID”. Un solo #id siempre supera a cualquier cantidad de clases — (0,1,0,0) gana a (0,0,999,0) porque B=1 > B=0 y los C y D no se evalúan.
  • El selector universal * vale 0. * no suma ningún punto. Es el selector de menor especificidad posible junto con los combinadores.
  • :not(), :is(), :where() tienen comportamiento especial. :not(X) tiene la especificidad del argumento X, no la de :not en sí. :is(A,B) tiene la especificidad del argumento más específico. :where() siempre vale 0 (útil para estilos base fáciles de sobreescribir).
  • Herencia no tiene especificidad. Un valor heredado pierde contra cualquier declaración directa en el hijo, sin importar la especificidad. Si un hijo heredó color: blue y definís * { color: red; }, el * con especificidad 0 gana sobre la herencia.
  • Las DevTools son el mejor debugger. Abrí DevTools → “Styles” y las declaraciones tachadas son las que perdieron la competencia de especificidad (o de orden). Junto a cada selector aparece su valor de especificidad calculado al hacer hover.

Vigente / legacy

El modelo de especificidad existe desde CSS1 y no tiene alternativa legacy. Lo que evolucionó: en CSS1/2 la notación era un número de tres dígitos (sin el A de inline, que se trataba diferente). La presentación como tupla (A,B,C,D) es la forma moderna estandarizada. @layer (CSS Cascade 5, 2022) agrega capas que tienen prioridad incluso sobre la especificidad, pero eso es para arquitecturas CSS avanzadas — no cambia el modelo básico.