Cómo se calcula la precedencia de los estilos CSS

Código CSS sobre fotografía de una cascada de fondo

Hemos visto qué estilos por defecto se aplican cuando no se declara ningún valor para una determinada propiedad CSS de un elemento. Pero ¿qué pasa cuando se declara más de uno?

La respuesta está en el propio nombre del lenguaje (hojas de estilo en cascada). La cascada es el proceso mediante el cual, en función de una serie de criterios, se ordenan por prioridad las declaraciones CSS en conflicto y se establece una «ganadora».

Tener claro cuáles son estos criterios es el mejor antídoto contra una de las mayores lacras de nuestro tiempo: ¡el CSS espagueti!

¿Cómo decide el navegador las prioridades de los estilos CSS?

La precedencia de las reglas CSS se establece en base a tres criterios: origen, especificidad y orden. De mayor a menor prioridad:

  1. Origen/importancia. Los estilos por defecto del navegador, por ejemplo, tienen menor precedencia que los del autor de la web (a no ser que estén marcados como !important).
  2. Especificidad. A igualdad de origen/importancia, los selectores más específicos son más prioritarios que los menos específicos (p. ej., veremos que #boton-enviar es más específico que input.boton-primario).
  3. Orden de aparición. Si sigue habiendo empate, se aplica la regla quien ríe el último ríe mejor (las reglas declaradas más abajo ganan a las declaradas anteriormente).
Animación mostrando maneras de influir en las precedencias CSS

Precedencia de los orígenes de cascada

Los estilos pueden venir de tres orígenes, de mayor a menor precedencia:

  1. Autor. Son los especificados por el autor del HTML, ya sea como hojas de estilo externas, hojas embebidas o atributos style.
  2. Usuario. Algunos navegadores, o extensiones de navegador, permiten al usuario añadir sus propias hojas de estilo personalizadas que modifican el aspecto de determinadas webs.
  3. Agente de usuario. Son los estilos por defecto que asigna el navegador (tamaño de los <h1>, cursivas para los <em>…).

Las declaraciones marcadas !important invierten la precedencia de los orígenes. Así, tenemos básicamente seis niveles de prioridad, de mayor a menor:

  1. Declaraciones importantes de agente de usuario.
  2. Declaraciones importantes de usuario.
  3. Declaraciones importantes de autor.
  4. Declaraciones normales de autor.
  5. Declaraciones normales de usuario.
  6. Declaraciones normales de agente de usuario.

Si dos declaraciones en conflicto están en el mismo nivel de prioridad (empate), entonces, y solo entonces, se pasa a evaluar las especificidades de sus selectores.

Cálculo de la especificidad

La especificidad de un selector se calcula contando por separado el número de:

  1.  #identificadores
  2. .clases (incluyendo :pseudoclases y [selectores de atributos])
  3. Tipos de elementos y ::pseudoelementos.

Por ejemplo, el selector ul.menu > li > a tiene especificidad (0, 1, 3), ya que tiene cero identificadores, una clase y tres tipos de elemento; el selector #btn-comprar tiene especificidad (1, 0, 0), etc.

Para comparar especificidades, se mira el primer número. Si hay empate, se compara el segundo número, y si sigue habiendo empate, entonces se compara el tercero. Si los tres son iguales, ambas especificidades son iguales.

A tener en cuenta:

  •  Los estilos especificados en atributos style se considera que tienen mayor especificidad que cualquier selector.
  • En una lista de selectores separados por comas (p. ej., .btn-social, .botonera .btn {…}, la especificidad de la regla es la del selector aplicable más específico (en el ejemplo, .botonera .btn → (0, 2, 0)).
  • El selector universal * aporta especificidad 0 (así, *.clase tiene igual especificidad que .clase).
  • Las pseudoclases que aceptan selectores como argumento (p. ej., :not(.cabecera h1)) tienen la especificidad del argumento que contienen (en el ejemplo, .cabecera h1 → (0, 1, 1)).

Orden de aparición

Si todo lo demás falla, determinar la precedencia es tan sencillo como que la última en llegar es la que gana.

Es como si a medida que vamos recorriendo el HTML, vamos extrayendo todo el código CSS y concatenándolo en una única hoja de estilo. Cuanto más abajo quede en esta hoja gigante, más prioritario es el estilo.

Esto incluye a los estilos importados mediante la sentencia @import, que, a efectos prácticos,  queda reemplazada por dichos estilos.

De ahí que sea tan frecuente encontrar estilos de esta guisa:

h1 {
  font-size: 36px; /* Para navegadores no compatibles con vw */ 
  font-size: 5.4vw; /* Esta declaración anula a la anterior */
}

¿Cómo dar prioridad a una regla CSS sobre otra?

Entonces, cuando tienes una colisión de estilos en tu CSS, y no se aplica la regla que debería aplicarse, ¿cómo corriges el problema?

Tienes básicamente dos opciones:

  1. Usar un selector con mayor especificidad.
  2. Con la misma especificidad, mover el estilo de modo que quede más abajo que el otro.

Hay otras dos posibilidades, pero no suelen ser aconsejables:

  1. Mover el estilo al atributo style del elemento. No es lo ideal, porque va en contra de la recomendación de separar presentación de contenido.
  2. Aplicar la anotación !important a las declaraciones a las que queremos dar prioridad. En raros casos es realmente necesario recurrir a esto. Abusa de esta técnica y pronto tu CSS será un caos imposible de mantener.


Deja un comentario

Tu dirección de correo electrónico no será publicada.

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.