MDN wants to learn about developers like you: https://qsurvey.mozilla.com/s3/MDN-dev-survey

En un artículo anterior hemos hablado de los distintos selectores CSS. Podemos encontrar varios selectores de reglas CSS afectando al mismo elemento, en estos casos, ¿Qué regla será la que finalmente se aplicará al elemento? Esto está regulado por un mecanismo llamado Cascada; también está relacionado con la Herencia (unos elementos toman "heredan" valores de las propiedades de sus padres, pero otros no). En este artículo definiremos el concepto de cascada, especificidad e importancia y cómo se heredan las propiedades de distintas reglas.

Prerrequisitos: Conocimientos básicos de ordenadores, SW básico instalado, conocimiento básico de manejo de ficheros, y HTML básico (ver Introducción a HTML). Y una idea de cómo funciona CSS (estudiar los arículos previos en este módulo).
Objetivo: Aprender sobre cascada y especificidad, y cómo funciona la herencia en CSS

Podemos dar estilo a un elemento desde distintos sitios que pueden interactuar de forma compleja. Esta interacción compleja hace potente a CSS, pero también puede hacerlo confuso y dificil de depurar. Este artículo tratará de clarificar algo esta complejidad; es normal que no lo comprendamos de manera inmediata — esta es una de las partes más duras de entender de la teoría de CSS. La idea es proporcionar un primer acercamiento y ofrecer una guía para futuras consultas sobre cascada y herencia.

La cascada

CSS (Cascading Style Sheets) ya nos indica que cascada es un concepto importante. A su nivel más básico indica que el orden de las reglas CSS importa, pero es algo más que eso. Que prevalezcan unos selectores sobre otros en la cascada depende de tres factores (en orden de importancia — los primeros prevalecen sobre los últimos):

  1. Importancia
  2. Especificidad
  3. Orden del código

Importancia

En CSS, hay un trozo de sintaxis que podemos usar para asegurarnos que una determinada regla siempre "gane" sobre todas las demás: !important.

Veamoslo en un ejemplo:

<p class="better">This is a paragraph.</p>
<p class="better" id="winning">One selector to rule them all!</p>
#winning {
  background-color: red;
  border: 1px solid black;
}

.better {
  background-color: gray;
  border: none !important;
}

p {
  background-color: blue;
  color: white;
  padding: 5px;
}

Que produce el siguiente resultado:

Veamos qué ha pasado:

  1. Vemos que se han aplicado los valores de color y padding, pero no el de background-color ¿Porqué? En realidad deberían haberse aplicado las tres pues las reglas declaradas más tarde normalmente prevalecen sobre las anteriores.
  2. Sin embargo, ganan las reglas anteriores, pues los selectores de ID/clase son más específicos que los selectores de elemento (lo veremos en la siguiente sección).
  3. Ambos elementos pertenecen a la class better, pero el 2do tiene además la id winning. Las IDs son más específicas que las clases (solo podemos tener un elemento para cada ID en una página, pero podemos tener varios elementos de la misma clase — los selectores ID son muy específicos en aquello que seleccionan), el fondo de color rojo y el borde de 1px negro se deberían aplicar al segundo elemento, y el primer elemento tomará el fondo gris, sin borde, como indica la clase.
  4. El 2do elemento toma el fondo rojo, pero no el borde. ¿Porqué? La declaración !important de la segunda regla — declarada después de (border: none) hará que esta declaración prevalezca sobre el valor del borde de la regla anterior, incluso aunque ID tenga una especificidad mayor.

Nota: La única forma de prevalecer sobre esta declaración !important será incluyendo otra declaración !important con la misma especificidad, posteriormente en el orden del código.

Es útil saber que !important existe para poder reconocerlo en el código escrito por otros, PERO, no debemos usarlo a menos que sea estrictamente necesario. Una de estas ocasiones la encontramos cuando estamos trabajando un CMS y no podemos editar los módulos principales de CSS, y queremos sobrescribir un estilo que no puede sobrescribirse de ningún otro modo. NO lo usaremos si lo podemos evitar. Pues !important cambia la forma de trabajo de la cascada, y puede causar problemas de dificil solución en el depurado de CSS, especialmente en CSS grandes.

Es útil conocer que la importancia de una declaración CSS depende de los estilos que son especificados en la misma — los usuarios pueden establecer estilos propios que prevalezcan sobre los del desarrollador. Si, por ejemplo, usuarios disminuidos visuales quisieran usar un tamaño de fuente el doble de lo normal en todas las páginas para facilitar su lectura.

De existir conflicto, las declaraciones se aplicarán en el siguiente orden, donde las últimas prevalecen sobre las primeras:

  1. Declaraciones en el documento de estilos del usuario (como los estilos por defecto del navegador, usados cuando no hay otros estilos fijados)
  2. Declaraciones normales en el documento de estilos del usuario (establecidos por el propio usuario)
  3. Declaraciones normales en el documento de estilos del autor (los estilos establcidos por nosotros, los desarrolladores web)
  4. Declaraciones !Important en el documento de estilos del autor
  5. Declaraciones !Important en el documento de estilos del usuario

Tiene sentido que el documento de estilos del desarrollador prevalezca sobre el del usuario, pues así se puede mantener el diseño establecido, pero en determinadas ocasiones los usuarios pueden tener buenas razones para sobrescribir los estilos del desarrollador como ya se ha mencionado anteriormente — esto se consigue usado la declaración !Important en sus reglas.

Especificidad

La Especificidad es en sí una medida de cómo de específico es un selector — cuantos elementos puede seleccionar. Como hemos mostrado en el ejemplo anterior, los selectores de elementos son poco específicos. Los selectores de clase son más específicos, por lo que prevalecen sobre los selectores de elementos. Los selectores ID son todavía más específicos, por lo tanto ganan frente a los de clase. La única forma de vencer a los selectores ID es usando la declaración !important.

La especificidad que tiene un selector se mide mediante 4 valores (o componentes) diferentes, podemos pensar en ellos como en 4 columnas de unidades de millar, centenas, decenas y unidades:

  1. Unidades de millar: Puntúa 1 en esta columna si la declaración está dentro de un atributo style (como las declaraciones que no tienen selectores, que su especificidad es siempre 1000). Sino puntúa 0.
  2. Centenas: Puntúa 1 en esta columna por cada selector ID contenido en el selector.
  3. Decenas: Puntúa 1 en esta columna para cada selector de clase, selector de atributo o de pseudo-clase contenidos en el selector.
  4. Unidades: Puntúa 1 en esta columna por cada selector de elemento o pseudo-elemento contenidos en el selector.

Nota: El selector universal (*), las combinaciones (+, >, ~, ' ') y la pseudo-clase de negación (:not) no afectan a la especificidad.

La siguiente tabla muestra algunos ejemplos sueltos para meternos en tarea. Intentemos comprenderlos y entender porqué tienen la especificidad que se les ha atribuido.

Selector 1000 100 10 1 Especif. Total
h1 0 0 0 1 0001
#important 0 1 0 0 0100
h1 + p::first-letter 0 0 0 3 0003
li > a[href=*"en-US"] > .inline-warning 0 0 2 2 0022
#important div > div > a:hover, inside a <style> element 1 1 1 3 1113

Nota: Si varios selectores tienen la misma importancia y especificidad, ganará el que se declara más tarde en el código Source order.

Antes de avanzar, veamos un ejemplo en acción. Dado el siguiente HTML:

<div id="outer" class="container">
  <div id="inner" class="container">
    <ul>
      <li class="nav"><a href="#">One</a></li>
      <li class="nav"><a href="#">Two</a></li>
    </ul>
  </div>
</div>

Y el correspondiente CSS para el ejemplo:

/* specificity: 0101 */
#outer a {
  background-color: red;
}

/* specificity: 0201 */
#outer #inner a {
  background-color: blue;
}

/* specificity: 0104 */
#outer div ul li a {
  color: yellow;
}

/* specificity: 0113 */
#outer div ul .nav a {
  color: white;
}

/* specificity: 0024 */
div div li:nth-child(2) a:hover {
  border: 10px solid black;
}

/* specificity: 0023 */
div li:nth-child(2) a:hover {
  border: 10px dashed black;
}

/* specificity: 0033 */
div div .nav:nth-child(2) a:hover {
  border: 10px double black;
}

a {
  display: inline-block;
  line-height: 40px;
  font-size: 20px;
  text-decoration: none;
  text-align: center;
  width: 200px;
  margin-bottom: 10px;
}

ul {
  padding: 0;
}

li {
  list-style-type: none;
}

El resultado que obtenemos de este código es como sigue:

¿Qué ha pasado aquí? Primero, solo nos interesan las primeras siete reglas del ejemplo, y como comprobaremos, hemo incluido sus valores de especificidad en un comentario antes de cada uno:

  • Los dos primeros selectores compiten para dar estilo al color de fondo del link — el segundo gana y hace que el fondo se establezca en color azul porque tiene un selector de ID más en la cadena: su especificidad es de 201 contra 101.
  • Los selectores tercero y cuarto compiten para dar estilo al color del texto de los links — el segundo gana y hace el texto blanco aun teniendo un elemento selector menos, el selector que falta se ha cambiado por un selector de clase, que vale 10 en lugar de 1. Con todo, gana la especificidad de 113 frente a 104.
  • Los selectores del 5–7 compiten para estilizar los marcos de los links cuando pasa el ratón. El selector 6 pierde contra el 5 con especificidades de 23 contra 24 — tiene un selector de elementos menos en la cadena. El selector 7 gana al 5 y al 6 — tiene el mismo número de sub-selectores en la cadena que el 5, pero uno de los elementos se ha cambiado por un selector de clase. Por lo que la especificidad ganadora es de 33 contra 23 y 24.

Nota: Si no lo hemos hecho aún, es momento de revisar los selectores una vez más para asegurarnos que comprendemos porqué se les ha otorgado los valores de especificidad mostrados.

Orden del código

Como hemos mencionado, si varios selectores competidores tienen la misma importancia y especificidad, el tercer factor que interviene para decidir qué regla vence es el orden del código — las últimas reglas prevalecen sobre las primeras. Por ejemplo:

p {
  color: blue;
}

/* This rule will win over the first one */
p {
  color: red;
}

Pero en el siguiente ejemplo, la primera regla gana porque el orden del código es sobrescrito por la especificidad:

/* This rule will win */
.footnote {
  color: blue;
}

p {
  color: red;
}

Una nota en la combinación de reglas

Cuando estudiamos la teoría sobre la cascada, y el porqué unos estilos se aplican y otros no, es que esto ocurre a nivel de las propiedades — unas propiedades prevalecen sobre otras, pero no reglas enteras prevaleciendo sobre otras reglas. Cuando varias reglas CSS apuntan al mismo elemento, todas se aplican sobre él. Solo después de esto se evalúan los conflictos entre las propiedades para saber qué estilos prevalecerán sobre los otros.

Veamos un ejemplo. Primero, el HTML:

<p>I'm <strong>important</strong></p>

Y ahora CSS para dar estilo:

/* specificity: 0002 */
p strong {
  background-color: khaki;
  color: green;
}

/* specificity: 0001 */
strong {
  text-decoration: underline;
  color: red;
}

Resultado:

En este ejemplo, por su especificidad, la primera regla de la propiedad color sobrescribe la propiedad de color de la segunda regla. No obstante, tanto el background-color de la primera regla y la text-decoration de la segunda regla se aplican sobre el elemento <strong>. Habrás notado que el texto de ese elemento está en negrita: esto procede del documento de estilo por defecto del navegador para el elemento <strong>.

Herencia

La herencia en CSS es la última pieza que necesitamos conocer para tener la información completa y comprender qué estilo se aplicará a un elemento. La idea es que unos elementos se heredarán por los elementos hijos, y otros no.

  • Por ejemplo, tiene sentido que font-family y color sean heredadas, pues nos facilita establecer un ancho de fuente básico aplicando una familia de fuentes al elemento <html>; después podemos reemplazar las fuentes de elementos individuales si es necesario. Sería realmente molesto tener que establecer la fuente base para cada elemento por separado.
  • Otro ejemplo: tiene sentido que margin, padding, border, y background-image NO se hereden. Imaginemos el lio de formato/estilo que ocurriría si aplicamos estas propiedades en un elemento y fuera heredado por todos y cada uno de sus hijos, y después tener que "desaplicarlas" a todos los elementos también.

Las propiedades que se heredan por defecto y las que no, viene marcado en gran medida por el sentido común. Pero para estar seguros podemos consultar la Referencia CSS — cada propiedad viene en una página que comienza con una tabla resumen que incluye diversos detalles sobre cada elemento, incluyendo si se hereda o no.

Control de la herencia

CSS dispone de tres valores especiales para manejar las herencias:

  • inherit : Este valor establece el valor de la propiedad de un elemento seleccionado en el mismo que su elemento padre.
  • initial : Este valor establece el valor de la propiedad de un elemento seleccionado en el valor por defecto que establece la hoja de estilos del navegador, si este no existe, la propiedad se hereda naturalmente, adoptando el valor de inherit.
  • unset : Este valor reestablece la propiedad a su valor natural, esto es: si la propiedad se hereda de forma natural entonces actuará como inherit, sino, actuará como initial.

El valor inherit es el más interesante — nos permite, de forma explícita, hacer que un elemento herede de su padre el valor de una propiedad.

Echemos un vistazo a un ejemplo. Primero, como siempre, el HTML:

<ul>
  <li>Default <a href="#">link</a> color</li>
  <li class="inherit">Inherit the <a href="#">link</a> color</li>
  <li class="initial">Reset the <a href="#">link</a> color</li>
  <li class="unset">Unset the <a href="#">link</a> color</li>
</ul>

Y el CSS para dar estilo:

body {
  color: green;
}

.inherit a {
  color: inherit;
}

.initial a {
  color: initial
}

.unset a {
  color: unset;
}

Resultado:

Veamos que ha pasado aquí:

  • Primero hemos puesto en verde el color de <body>.
  • Como la propiedad color es heredada naturalmente, todos los elementos hijos de body tendrán color verde. Cabe señalar que los navegadores establecen por defecto los links en color azul en vez de permitir heredar naturalmente la propiedad color, entonces el primer link de la lista será azul.
  • La segunda regla establece que los links dentro de un elemento con la clase inherit hereden el color de su padre. En este caso, el link hereda el color de su padre <li>, que por defecto, hereda su color de su padre <ul>, que por último hereda el color del elemento <body>, cuyo color se estableció a green en la primera regla.
  • La tercera regla selecciona cualquier link dentro de un elemento con la clase initial y establece el color a initial. Normalmente, el valor inicial de los navegadores para el color de texto es el negro, luego este link será negro.
  • La última regla selecciona todos los links dentro de los elementos con la clase unset y pone su valor a unset — no establece valor. Al ser la propiedad color una propiedad heredada naturalmente actúa como si la clase fuera inherit. En consecuencia, este link se pondrá al mismo color que body — esto es verde.

Aprendizaje activo: jugando con la cascada

En este aprendizaje activo queremos que experimentes escribiendo una única regla que modifique el color y el color de fondo aplicado por defecto a los links. ¿Puedes usar uno de los valores especiales que hemos visto en la sección Control_de_la_herencia para escribir una declaración en una nueva regla que inicialice el color negro a blanco sin usar un valor de color real?

Si te equivocas puedes comenzar de nuevo pulsando el botón Reset. Si te atrancas, pulsa el botón Show solution para ver una respuesta válida.

¿Qué es lo siguiente?

Si hemos comprendido la mayoría de este artículo, ¡Bien hecho! — has comenzado a familiarizarte con la mecánica de funcionamiento de CSS. El ultimo punto de la teoría central es el modelo de cajas, que será lo siguiente que veamos.

Si no has pillado del todo la cascada, la especificidad y la herencia, ¡No te apures! Es de verdad lo más complicado que hemos visto hasta ahora en el curso, y es algo que incluso los desarrolladores web profesionales encuentran complicado. Te animamos a volver a este artículo mientras continuas con el curso, y a que reflexiones sobre el contenido. Consultalo si experimentas problemas con estilos que no funcionan de la manera que esperabas. Puede que sea un problema de especificidad.

Etiquetas y colaboradores del documento

 Colaboradores en esta página: javierpolit
 Última actualización por: javierpolit,