Capítulo 4. Especificidad, herencia y cascada

Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com

Los capítulos 2 y 3 mostraron cómo la estructura del documento y los selectores CSS te permiten aplicar una amplia variedad de estilos a los elementos. Sabiendo que todo documento válido genera un árbol estructural, puedes crear selectores que se dirijan a los elementos en función de sus ancestros, atributos, elementos hermanos, etc. El árbol estructural es lo que permite que funcionen los selectores y también es fundamental para un aspecto igualmente crucial de CSS: la herencia.

La herencia es el mecanismo por el que algunos valores de propiedad se transmiten de un elemento a otro descendiente. Al determinar qué valores deben aplicarse a un elemento, un agente de usuario debe tener en cuenta no sólo la herencia, sino también la especificidad de las declaraciones, así como el origen de las propias declaraciones. Este proceso de consideración es lo que se conoce como cascada.

Exploraremos la interrelación entre estos tres mecanismos -especificidad, herencia y cascada- en este capítulo. Por ahora, la diferencia entre los dos últimos puede resumirse así: cuando escribimos h1 {color: red; color: blue;}, el <h1> se vuelve azul debido a la cascada, y cualquier <span> dentro del <h1> también se vuelve azul debido a la herencia.

Sobre todo, independientemente de lo abstractas que puedan parecer las cosas, ¡sigue adelante! Tu perseverancia se verá recompensada.

Especificidad

sabes por los Capítulos 2 y 3 que puedes seleccionar elementos utilizando una amplia variedad de medios. De hecho, a menudo un mismo elemento puede ser seleccionado por dos o más reglas, cada una con su propio selector. Consideremos los tres pares de reglas siguientes. Supongamos que cada par coincidirá con el mismo elemento:

h1 {color: red;}
body h1 {color: green;}

h2.grape {color: purple;}
h2 {color: silver;}

html > body table tr[id="totals"] td ul > li {color: maroon;}
li#answer {color: navy;}

Sólo una de las dos reglas de cada par puede aplicarse, o "ganar", ya que los elementos emparejados sólo pueden ser de un color a la vez. ¿Cómo sabemos cuál ganará?

La respuesta se encuentra en la especificidad de cada selector. Para cada regla, el agente de usuario (es decir, un navegador web) evalúa la especificidad del selector y atribuye la especificidad a cada declaración de la regla dentro de la capa en cascada que tenga precedencia. Cuando un elemento tiene dos o más declaraciones de propiedades en conflicto, ganará la que tenga mayor especificidad.

Nota

Esto no es todo en cuanto a la resolución de conflictos, que es un poco más complicada de lo que puede abarcar un solo párrafo. Por ahora, ten en cuenta que la especificidad de un selector sólo se compara con otros selectores que comparten el mismo origen y la misma capa en cascada. Cubriremos esos términos y otros más en "La cascada".

La especificidad de un selector viene determinada por los componentes del propio selector. Un valor de especificidad puede expresarse en tres partes, así: 0,0,0. La especificidad real de un selector se determina de la siguiente manera:

  • Por cada valor del atributo ID dado en el selector, añade 1,0,0.

  • Por cada valor de atributo de clase, selección de atributo o pseudoclase dado en el selector, añade 0,1,0.

  • Para cada elemento y pseudoelemento dado en el selector, añade 0,0,1.

  • Los combinadores no aportan nada a la especificidad.

  • Todo lo que aparece dentro de una pseudoclase :where(), y el selector universal, añade 0,0,0. (Aunque no aportan nada al peso de especificidad, sí emparejan elementos, a diferencia de los combinadores).

  • La especificidad de una pseudoclase :is(), :not() o :has() es igual a la especificidad del selector más específico de su argumento de lista de selectores.

Por ejemplo, los selectores de las reglas siguientes dan lugar a las especificidades indicadas:

h1 {color: red;}                     /* specificity = 0,0,1 */
p em {color: purple;}                /* specificity = 0,0,2 */
.grape {color: purple;}              /* specificity = 0,1,0 */
*.bright {color: yellow;}            /* specificity = 0,1,0 */
p.bright em.dark {color: maroon;}    /* specificity = 0,2,2 */
#id216 {color: blue;}                /* specificity = 1,0,0 */
*:is(aside#warn, code) {color: red;} /* specificity = 1,0,1 */
div#sidebar *[href] {color: silver;} /* specificity = 1,1,1 */

Si un elemento <em> coincide tanto con la segunda como con la quinta regla de este ejemplo, ese elemento será de color granate porque la especificidad de la sexta regla supera a la de la segunda.

Fíjate especialmente en , el penúltimo selector, *:is(aside#warn, code). La pseudoclase :is() forma parte de un pequeño grupo de pseudoclases cuya especificidad es igual al selector más específico de la lista de selectores. En este caso, la lista de selectores es aside#warn, code. El selector compuesto aside#warn tiene una especificidad de 1,0,1, y el selector code tiene una especificidad de 0,0,1. Así, toda la parte del selector :is() se ajusta a la especificidad del selector aside#warn.

Ahora, volvamos a los pares de reglas de antes y completemos las especificidades:

h1 {color: red;}         /* 0,0,1 */
body h1 {color: green;}  /* 0,0,2 (winner)*/

h2.grape {color: purple;}  /* 0,1,1 (winner) */
h2 {color: silver;}        /* 0,0,1 */

html > body table tr[id="totals"] td ul > li {color: maroon;}  /* 0,1,7 */
li#answer {color: navy;}                                       /* 1,0,1
   (winner) */

Hemos indicado la regla ganadora en cada par; en cada caso, es porque la especificidad es mayor. Observa cómo están enumeradas y que el orden de las reglas no importa aquí.

En el segundo par, el selector h2.grape gana porque tiene una clase extra: 0,1,1 gana a 0,0,1. En el tercer par, la segunda regla gana porque 1,0,1 gana a 0,1,7. De hecho, el valor de especificidad 0,1,0 ganaría al valor 0,0,13.

Esto ocurre porque los valores se comparan de izquierda a derecha. Una especificidad de 1,0,0 ganará a cualquier especificidad que empiece por 0, independientemente de cuáles sean el resto de los números. Así que 1,0,1 gana a 0,1,7 porque 1 en la primera posición del primer valor gana a 0 en la primera posición del segundo valor.

Declaraciones y especificidad

Una vez determinada la especificidad de un selector, el valor de especificidad se conferirá a todas sus declaraciones asociadas. Considera esta regla:

h1 {color: silver; background: black;}

A efectos de especificidad, el agente de usuario debe tratar la regla como si estuviera "desagrupada" en reglas separadas. Así, el ejemplo anterior se convertiría en lo siguiente:

h1 {color: silver;}
h1 {background: black;}

Ambos tienen una especificidad de 0,0,1, y ése es el valor que se confiere a cada declaración. El mismo proceso de división ocurre también con un selector agrupado. Dada la regla

h1, h2.section {color: silver; background: black;}

el agente de usuario lo trata como si fuera lo siguiente

h1 {color: silver;}             /* 0,0,1 */
h1 {background: black;}         /* 0,0,1 */
h2.section {color: silver;}     /* 0,1,1 */
h2.section {background: black;} /* 0,1,1 */

Esto resulta importante cuando varias reglas coinciden con el mismo elemento y algunas de las declaraciones chocan. Por ejemplo, considera estas reglas:

h1 + p {color: black; font-style: italic;}              /* 0,0,2 */
p {color: gray; background: white; font-style: normal;} /* 0,0,1 */
*.callout {color: black; background: silver;}           /* 0,1,0 */

Cuando se aplique a la siguiente marca, el contenido se mostrará como se muestra en la Figura 4-1:

<h1>Greetings!</h1>
<p class="callout">
It's a fine way to start a day, don't you think?
</p>
<p>
There are many ways to greet a person, but the words are not as important
as the act of greeting itself.
</p>
<h1>Salutations!</h1>
<p>
There is nothing finer than a hearty welcome from one's neighbor.
</p>
<p class="callout">
Although a steaming pot of fresh-made jambalaya runs a close second.
</p>
css5 0401
Figura 4-1. Cómo afectan las distintas reglas a un documento

En cada caso de , el agente de usuario determina qué reglas coinciden con un elemento dado, calcula todas las declaraciones asociadas y sus especificidades, determina qué reglas ganan y, a continuación, aplica las ganadoras al elemento para obtener el resultado estilizado. Estas maquinaciones deben realizarse en cada elemento, selector y declaración. Afortunadamente, el agente de usuario lo hace todo de forma automática y casi instantánea. Este comportamiento es un componente importante de la cascada, de la que hablaremos más adelante en este capítulo.

Resolver coincidencias múltiples

Cuando un elemento coincide con más de un selector en un selector agrupado, se utiliza el selector más específico. Considera el siguiente CSS:

li,            /* 0,0,1 */
.quirky,       /* 0,1,0 */
#friendly,     /* 1,0,0 */
li.happy.happy.happy#friendly { /* 1,3,1 */
   color: blue;
}

Aquí tenemos una regla con un selector agrupado, y cada uno de los selectores individuales tiene una especificidad muy diferente. Ahora supongamos que encontramos esto en nuestro HTML

<li class="happy quirky" id="friendly">This will be blue.</li>

¡Cada uno de los selectores del selector agrupado se aplica al elemento de la lista! ¿Cuál se utiliza a efectos de especificidad? El más específico. Así, en este ejemplo, el azul se aplica con una especificidad de 1,3,1.

Te habrás dado cuenta de que hemos repetido el nombre de la clase happy tres veces en uno de los selectores. Se trata de un truco que puede utilizarse con clases, atributos, pseudoclases e incluso selectores de ID para aumentar la especificidad. Ten cuidado con ello, ya que inflar artificialmente la especificidad puede crear problemas en el futuro: puede que quieras anular esa regla con otra, y esa regla necesitará aún más clases encadenadas.

Especificidad del selector a cero

El selector universal no contribuye a la especificidad. Tiene una especificidad de 0,0,0, que es diferente de no tener especificidad (como veremos en "Herencia"). Por tanto, dadas las dos reglas siguientes, un párrafo que descienda de un <div> será negro, pero todos los demás elementos serán grises:

div p {color: black;} /* 0,0,2 */
* {color: gray;}      /* 0,0,0 */

Esto significa que la especificidad de un selector que contiene un selector universal junto con otros selectores no cambia por la presencia del selector universal. Los dos selectores siguientes tienen exactamente la misma especificidad:

div p         /* 0,0,2 */
body * strong /* 0,0,2 */

Lo mismo ocurre con la pseudoclase :where(), independientemente de los selectores que pueda haber en su lista de selectores. Así, :where(aside#warn, code) tiene una especificidad de 0,0,0.

Los combinadores, incluyendo ~, >, +, y el carácter espacio, no tienen especificidad alguna, ni siquiera especificidad cero. Por tanto, no influyen en la especificidad global de un selector.

Especificidad de los selectores de ID y atributos

Es importante que tengas en cuenta la diferencia de especificidad entre un selector de ID y un selector de atributo cuyo objetivo es un atributo id. Volviendo al tercer par de reglas del código de ejemplo, encontramos lo siguiente:

html > body table tr[id="totals"] td ul > li {color: maroon;} /* 0,1,7 */
li#answer {color: navy;}                                      /* 1,0,1 (wins) */

El selector de ID (#answer) de la segunda regla contribuye 1,0,0 a la especificidad global del selector. En la primera regla, sin embargo, el selector de atributo ([id="totals"]) contribuye con 0,1,0 a la especificidad global. Así, dadas las siguientes reglas, el elemento con un id de meadow será verde:

#meadow {color: green;}      /* 1,0,0 */
*[id="meadow"] {color: red;} /* 0,1,0 */

Importancia

A veces una declaración es tan importante que pesa más que cualquier otra consideración. CSS llama a estas declaraciones importantes (espero que por razones obvias) y te permite marcarlas insertando la bandera !important justo antes del punto y coma final de una declaración:

p.dark {color: #333 !important; background: white;}

Aquí, el valor de color de #333 está marcado con la bandera !important, mientras que el valor de fondo de white no lo está. Si deseas marcar ambas declaraciones como importantes, cada declaración necesita su propia bandera !important:

p.dark {color: #333 !important; background: white !important;}

Debes colocar correctamente la bandera !important, o la declaración puede quedar invalidada: !important siempre va al final de una declaración, justo antes del punto y coma. Esta colocación es especialmente crítica cuando se trata de propiedades que permiten valores que contienen varias palabras clave, como font:

p.light {color: yellow; font: smaller Times, serif !important;}

Si !important se colocara en cualquier otro lugar de la declaración font, probablemente se invalidaría toda la declaración y no se aplicaría ninguno de sus estilos.

Nota

Somos conscientes de que para aquellos de vosotros que procedéis de un entorno de programación, la sintaxis de este símbolo se traduce instintivamente como "no importante". Por la razón que sea, se eligió la almohadilla (!) como delimitador de las marcas importantes, y no significa "no" en CSS, por mucho que otros lenguajes le den ese mismo significado. Esta asociación es desafortunada, pero estamos atascados con ella.

Las declaraciones marcadas como !important no tienen un valor de especificidad especial, sino que se consideran por separado de las declaraciones sin importancia. En efecto, todas lasdeclaraciones !importantse agrupan, y los conflictos de especificidad se resuelven dentro de ese grupo. Del mismo modo, todas las declaraciones sin importancia se consideran como un grupo, y cualquier conflicto dentro del grupo sin importancia se resuelve como se ha descrito anteriormente. Así, en cualquier caso en que una declaración importante y una no importante entren en conflicto, siempre ganará la declaración importante (a menos que el agente de usuario o el usuario hayan declarado la misma propiedad como importante, lo que verás más adelante en el capítulo).

La Figura 4-2 ilustra el resultado de las siguientes reglas y fragmento de marcado:

h1 {font-style: italic; color: gray !important;}
.title {color: black; background: silver;}
* {background: black !important;}
<h1 class="title">NightWing</h1>
css5 0402
Figura 4-2. Las reglas importantes siempre ganan
Advertencia

En general, es una mala práctica utilizar !important en tu CSS, y rara vez es necesario. Si te encuentras recurriendo a !important, detente y busca otras formas de obtener el mismo resultado sin utilizar!important. Las capas en cas cada son una de esas posibilidades; consulta "Ordenar por capa en cascada" para más detalles.

Herencia

Otro concepto clave de para entender cómo se aplican los estilos a los elementos es la herencia. La herencia es el mecanismo por el cual algunos estilos se aplican no sólo a un elemento concreto, sino también a sus descendientes. Si se aplica un color a un elemento <h1>, por ejemplo, ese color se aplica a todo el texto dentro de <h1>, incluso al texto encerrado dentro de elementos hijos de ese <h1>:

h1 {color: gray;}
<h1>Meerkat <em>Central</em></h1>

Tanto el texto ordinario <h1> como el texto <em> están coloreados en gris porque el elemento <em> hereda el valor de color del <h1>. Si los valores de las propiedades no pudieran ser heredados por los elementos descendientes, el texto <em> sería negro, no gris, y tendríamos que colorear los elementos por separado.

Considera una lista desordenada. Digamos que aplicamos un estilo de color: gray; para los elementos de <ul>:

ul {color: gray;}

Esperamos que el estilo aplicado a un <ul> se aplique también a sus elementos de lista, así como a cualquier contenido de esos elementos de lista, incluido el marcador (es decir, la viñeta situada junto a cada elemento de lista). Gracias a la herencia, eso es exactamente lo que ocurre, como demuestra la Figura 4-3.

css5 0403
Figura 4-3. Herencia de estilos

Es más fácil ver cómo funciona la herencia recurriendo a un diagrama de árbol de un documento. La Figura 4-4 muestra el diagrama de árbol de un documento muy parecido al documento muy sencillo de la Figura 4-3.

css5 0404
Figura 4-4. Un diagrama de árbol simple

Cuando se aplica la declaración color: gray; al elemento <ul>, dicho elemento adopta esa declaración. A continuación, el valor se propaga por el árbol a los elementos descendientes y continúa hasta que no quedan más descendientes que hereden el valor. Los valores nunca se propagan hacia arriba; un elemento nunca transmite valores a sus antepasados.

Nota

La regla de propagación ascendente en HTML tiene una notable excepción: los estilos de fondo aplicados al elemento <body> pueden pasarse al elemento <html>, que es el elemento raíz del documento y, por tanto, define su lienzo. Esto sólo ocurre si el elemento <body> tiene un fondo definido y el elemento <html> no. Algunas otras propiedades comparten este comportamiento de cuerpo a raíz, como overflow, pero sólo ocurre con el elemento <body>. Ningún otro elemento corre el riesgo de heredar propiedades de un descendiente.

La herencia es una de esas cosas del CSS tan básicas que casi nunca piensas en ella a menos que sea necesario. Sin embargo, debes tener en cuenta un par de cosas.

En primer lugar, ten en cuenta que muchas propiedades de no se heredan, generalmente para evitar resultados no deseados. Por ejemplo, la propiedad border (que se utiliza para establecer bordes en los elementos) no se hereda. Un rápido vistazo a la Figura 4-5 revela por qué es así. Si los bordes se heredaran, los documentos estarían mucho más recargados, a menos que el autor se tomara la molestia de desactivar los bordes heredados.

css5 0405
Figura 4-5. Por qué no se heredan los bordes

Resulta que la mayoría de las propiedades del modelo de caja -incluidos los márgenes, el relleno, los fondos y los bordes- no se heredan por la misma razón. Al fin y al cabo, probablemente no querrías que todos los enlaces de un párrafo heredaran un margen izquierdo de 30 píxeles de su elemento padre.

En segundo lugar, los valores heredados no tienen especificidad alguna, ni siquiera especificidad cero. Esto parece una distinción académica hasta que analizas las consecuencias de la falta de especificidad heredada. Considera las siguientes reglas y fragmento de marcado y compáralos con el resultado que se muestra en la Figura 4-6:

* {color: gray;}
h1#page-title {color: black;}
<h1 id="page-title">Meerkat <em>Central</em></h1>
<p>
Welcome to the best place on the web for meerkat information!
</p>
css5 0406
Figura 4-6. La especificidad cero vence a la no especificidad

Como el selector universal se aplica a todos los elementos y tiene especificidad cero, el valor de gray de su declaración de color gana al valor heredado de black, que no tiene especificidad alguna. (Y ahora entenderás por qué hemos indicado que :where() y el selector universal tienen especificidad 0,0,0: no añaden peso, pero sí coinciden con los elementos). Por lo tanto, el elemento <em> se muestra gris en lugar de negro.

Este ejemplo ilustra vívidamente uno de los problemas potenciales del uso indiscriminado del selector universal. Como puede coincidir con cualquier elemento o pseudoelemento, el selector universal a menudo provoca un cortocircuito en la herencia. Esto puede solucionarse, pero suele ser más sensato evitar el problema en primer lugar no utilizando el selector universal por sí mismo indiscriminadamente.

La falta total de especificidad de los valores heredados no es un punto trivial. Por ejemplo, supón que se ha escrito una hoja de estilos de forma que todo el texto de una barra de herramientas debe ser blanco sobre negro:

#toolbar {color: white; background: black;}

Esto funcionará siempre que el elemento con un id de toolbar no contenga nada más que texto sin formato. Sin embargo, si el texto dentro de este elemento son todos hipervínculos (elementosa ), entonces los estilos del agente de usuario para hipervínculos tomarán el control. En un navegador web, esto significa que probablemente se colorearán de azul, ya que la hoja de estilos interna del navegador probablemente contenga una entrada como ésta:

a:link {color: blue;}

Para superar este problema, debes declarar algo como esto:

#toolbar {color: white; background: black;}
#toolbar a:any-link {color: white;}

Si diriges una regla directamente a los elementos a de la barra de herramientas, obtendrás el resultado que se muestra en la Figura 4-7.

css5 0407
Figura 4-7. Asignación directa de estilos a los elementos relevantes

Otra forma de obtener el mismo resultado es utilizar el valor inherit, tratado en el capítulo siguiente. Podemos modificar el ejemplo anterior así

#toolbar {color: white; background: black;}
#toolbar a:link {color: inherit;}

Esto también conduce al resultado mostrado en la Figura 4-7, porque el valor de color se hereda explícitamente gracias a una regla asignada cuyo selector tiene especificidad.

La Cascada

A lo largo de este capítulo , hemos eludido una cuestión bastante importante: ¿qué ocurre cuando dos reglas de igual especificidad se aplican al mismo elemento? ¿Cómo resuelve el navegador el conflicto? Por ejemplo, considera las siguientes reglas:

h1 {color: red;}
h1 {color: blue;}

¿Cuál gana? Ambas tienen una especificidad de 0,0,1, por lo que tienen el mismo peso y deberían aplicarse las dos. No puede ser así porque el elemento no puede ser a la vez rojo y azul. Entonces, ¿cuál será?

Por fin aparece el nombre de Hojas de Estilo en Cascada: CSS se basa en un método para hacer que los estilos se combinen en cascada, lo que es posible combinando la herencia y la especificidad con unas pocas reglas. Las reglas de cascada para CSS son las siguientes:

  1. Busca todas las reglas que contengan un selector que coincida con un elemento dado.

  2. Ordena por peso explícito todas las declaraciones que se aplican al elemento dado.

  3. Ordena por origen todas las declaraciones que se apliquen al elemento dado. Hay tres orígenes básicos: autor, lector y agente de usuario. En circunstancias normales, los estilos del autor (es decir, tus estilos como autor de la página) ganan sobre los estilos del lector, y tanto los estilos del autor como los del lector anulan los estilos por defecto del agente de usuario. Esto se invierte en las reglas marcadas como !important, en las que los estilos del agente de usuario prevalecen sobre los estilos del autor, y ambos prevalecen sobre los estilos del lector.

  4. Ordena todas las declaraciones que se aplican al elemento dado por contexto de encapsulación. Si un estilo se asigna a través de un DOM sombra, por ejemplo, tiene un contexto de encapsulación para todos los elementos dentro de ese mismo DOM sombra y no se aplica a elementos fuera de ese DOM sombra. Esto permite que los estilos encapsulados anulen los estilos heredados de fuera del DOM sombra.

  5. Ordena todas las declaraciones de en función de si están unidas a un elemento. Los estilos asignados mediante un atributo style se adjuntan a un elemento. Los estilos asignados desde una hoja de estilo, ya sea externa o incrustada, no lo están.

  6. Ordena todas las declaraciones de por capa en cascada. Para los estilos de peso normal, cuanto más tarde aparezca por primera vez una capa en cascada en el CSS, mayor será la precedencia. Los estilos sin capa se consideran parte de una pseudocapa final "por defecto", que tiene mayor precedencia que los estilos en capas creadas explícitamente. Para los estilos de peso importante, cuanto antes aparezca una capa en cascada en el CSS, mayor será su precedencia, y todos los estilos de peso importante en capas creadas explícitamente ganan a los estilos de la capa por defecto, importantes o no. Las capas en cascada pueden aparecer en cualquier origen.

  7. Ordena todas las declaraciones de que se aplican al elemento dado por especificidad. Los elementos con mayor especificidad tienen más peso que los de menor especificidad.

  8. Ordena todas las declaraciones de que se aplican al elemento dado por orden de aparición. Cuanto más tarde aparezca una declaración en la hoja de estilos o en el documento, más peso tendrá. Las declaraciones que aparecen en una hoja de estilos importada se consideran anteriores a todas las declaraciones de la hoja de estilos que las importa.

Para tener claro cómo funciona todo esto, veamos ejemplos que ilustren algunas de las reglas en cascada.

Clasificación por importancia y origen

Si a un elemento se le aplican dos normas y una de ellas está marcada como !important, la norma importanteprevalece:

p {color: gray !important;}
<p style="color: black;">Well, <em>hello</em> there!</p>

Aunque se asigna un color en el atributo style del párrafo, la regla !important gana, y el párrafo es gris. Esto ocurre porque la ordenación por !important tiene mayor prioridad que la ordenación por estilos adjuntos al elemento (style=""). El gris también lo hereda el elemento <em>.

Ten en cuenta que si se añade !important al estilo inline en esta situación, será el ganador. Así, dado lo siguiente, el párrafo (y su elemento descendiente) será negro:

p {color: gray !important;}
<p style="color: black !important;">Well, <em>hello</em> there!</p>

Si la importancia de es la misma, se tiene en cuenta el origen de una regla. Si un elemento coincide con estilos normales tanto en la hoja de estilos del autor como en la del lector, se utilizan los estilos del autor. Por ejemplo, supongamos que los siguientes estilos proceden de los orígenes indicados:

p em {color: black;}    /* author's stylesheet */

p em {color: yellow;}   /* reader's stylesheet */

En este caso, el texto enfatizado dentro de los párrafos se colorea de negro, no de amarillo, porque los estilos de autor ganan a los estilos de lector. Sin embargo, si se marcan ambas reglas!importantla situación cambia:

p em {color: black !important;}    /* author's stylesheet */

p em {color: yellow !important;}   /* reader's stylesheet */

Ahora el texto destacado en los párrafos será amarillo, no negro.

Resulta que los estilos por defecto del agente de usuario -que suelen estar influidos por las preferencias del usuario- se incluyen en este paso. Las declaraciones de estilo por defecto son las menos influyentes de todas. Por lo tanto, si una regla definida por el autor se aplica a las anclas (por ejemplo, declarándolas white), esta regla anula los valores por defecto del agente de usuario.

En resumen, CSS tiene ocho niveles básicos a considerar en cuanto a la precedencia de las declaraciones. En orden de mayor a menor precedencia, son los siguientes:

  1. Declaraciones de transición (ver Capítulo 18)

  2. Declaraciones importantes del agente de usuario

  3. Declaraciones importantes del lector

  4. Declaraciones importantes del autor

  5. Declaraciones de animación (ver Capítulo 19)

  6. Declaraciones normales del autor

  7. Declaraciones normales del lector

  8. Declaraciones del agente de usuario

Así, un estilo de transición anulará todas las demás reglas, independientemente de si esas otras reglas están marcadas como !important o de qué origen procedan.

Ordenar por Elemento Adjunto

Los estilos se pueden adjuntar a un elemento utilizando un atributo de marcado como style. Se denominan estilos adjuntos a un elemento, y sólo se ven superados por consideraciones de origen y peso.

Para entenderlo, considera la siguiente regla y fragmento de marcado:

h1 {color: red;}
<h1 style="color: green;">The Meadow Party</h1>

Dado que la regla se aplica al elemento <h1>, probablemente seguirías esperando que el texto de <h1> fuera verde. Esto ocurre porque toda declaración en línea está unida a un elemento y, por tanto, tiene un peso mayor que los estilos que no están unidos a un elemento, como la regla color: red.

Esto significa que incluso los elementos con atributos id que coincidan con una regla obedecerán la declaración de estilo en línea. Modifiquemos el ejemplo anterior para incluir un id:

h1#meadow {color: red;}
<h1 id="meadow" style="color: green;">The Meadow Party</h1>

Gracias al peso de la declaración en línea, el texto del elemento <h1> seguirá siendo verde.

Recuerda que los estilos en línea suelen ser una mala práctica, así que intenta no utilizarlos si es posible.

Ordenar por capa en cascada

Las capas en cascada permiten a los autores de agrupar estilos para que compartan un nivel de precedencia dentro de la cascada. Esto puede sonar a !important; en algunos aspectos son similares, pero en otros, muy diferentes. Esto es más fácil de demostrar que de describir. La posibilidad de crear capas en cascada significa que los autores pueden equilibrar diversas necesidades, como las necesidades de una biblioteca de componentes, con las necesidades de una página específica o de una parte de una aplicación web.

Nota

Las capas en cascada se introdujeron en CSS a finales de 2021, por lo que sólo son compatibles con los navegadores lanzados a partir de ese momento.

Si declaraciones en conflicto se aplican a un elemento y todas tienen el mismo peso explícito y origen, y ninguna está adjunta a un elemento, a continuación se ordenan por capas en cascada. El orden de precedencia de las capas se establece por el orden en que éstas se declaran o utilizan por primera vez, teniendo precedencia las capas declaradas más tarde sobre las declaradas anteriormente para los estilos normales. Considera lo siguiente:

@layer site {
     h1 {color: red;}
}
@layer page {
     h1 {color: blue;}
}

Estos elementos <h1> se colorearán de azul. Esto se debe a que la capa page aparece más tarde en el CSS que la capa site, y por tanto tiene mayor precedencia.

Cualquier estilo que no forme parte de una capa en cascada con nombre se asigna a una capa implícita "por defecto", que tiene mayor precedencia que cualquier capa con nombre para las reglas sin importancia. Supongamos que modificamos el ejemplo anterior de la siguiente manera:

h1 {color: maroon;}
@layer site {
     h1 {color: red;}
}
@layer page {
     h1 {color: blue;}
}

Los elementos <h1> serán ahora de color granate, porque la capa implícita "por defecto" a la que pertenece h1 {color: maroon;} tiene mayor precedencia que cualquier capa con nombre.

También puedes definir un orden de precedencia específico para las capas en cascada con nombre. Considera el siguiente CSS:

@layer site, page;

@layer page {
   h1 {color: blue;}
}

@layer site {
   h1 {color: red;}
}

Aquí, la primera línea define un orden de precedencia para las capas: a la capa page se le dará mayor precedencia que a la capa site para reglas de peso normal como las que se muestran en el ejemplo. Así, en este caso, los elementos <h1> serán azules, porque cuando se ordenan las capas, a page se le da más precedencia que a site. Para las reglas marcadas como importantes, el orden de precedencia se invierte. Así, si ambas reglas estuvieran marcadas como !important, la precedencia se invertiría y los elementos <h1> serían rojos.

Hablemos un poco más de cómo funcionan específicamente las capas en cascada, sobre todo porque son muy nuevas en CSS. Supongamos que quieres definir tres capas: una para los estilos básicos del sitio, otra para los estilos de páginas individuales y otra para una biblioteca de componentes cuyos estilos se importan de una hoja de estilos externa. El CSS podría tener este aspecto:

@layer site, page;
@import url(/assets/css/components.css) layer(components);

Esta ordenación hará que los estilos components de peso normal anulen y los estilos page y site de peso normal, y los estilos page de peso normal anularán sólo los estilos site de peso normal. A la inversa, los estilos site importantes anularán todos los estilos page y components, ya sean de peso importante o normal, y los estilos page importantes anularán todos los estilos components.

He aquí un pequeño ejemplo de cómo podrían gestionarse las capas:

@layer site, component, page;
@import url(/c/lib/core.css) layer(component);
@import url(/c/lib/widgets.css) layer(component);
@import url(/c/site.css) layer(site);

@layer page {
   h1 {color: maroon;}
   p {margin-top: 0;}
}

@layer site {
   body {font-size: 1.1rem;}
   h1 {color: orange;}
   p {margin-top: 0.5em;}
}

p {margin-top: 1em;}

Este ejemplo tiene tres hojas de estilo importadas, una de las cuales se asigna a la capa site y dos de ellas están en la capa component. Luego se asignan algunas reglas a la capa page, y un par de reglas se colocan en la capa site. Las reglas del bloque @layer site {} se combinarán con las reglas de /c/site.css en una única capa site.

Después, hay una regla fuera de las capas explícitas en cascada, lo que significa que forma parte de la capa implícita "por defecto". Las reglas de esta capa por defecto anularán los estilos de cualquiera de las otras capas. Así, dado el código mostrado, los párrafos tendrán márgenes superiores de 1em.

Pero antes de todo eso, una directiva establece el orden de precedencia de las capas nombradas: page anula component y site, y component anula site. He aquí cómo se agrupan esas distintas reglas en lo que respecta a la cascada, con comentarios para describir su colocación en la ordenación:

/* 'site' layer is the lowest weighted */
@import url(/c/site.css) layer(site);
@layer site {
   body {font-size: 1.1rem;}
   h1 {color: orange;}
   p {margin-top: 0.5em;}
}

/* 'component' layer is the next-lowest weighted */
@import url(/c/lib/core.css) layer(component);
@import url(/c/lib/widgets.css) layer(component);

/* 'page' layer is the next-highest weighted */
@layer page {
   h1 {color: maroon;}
   p {margin-top: 0;}
}

/* the implicit layer is the highest weighted */
p {margin-top: 1em;}

Como puedes ver, cuanto más tarde aparece una capa en la ordenación de las capas, más peso le da el algoritmo de ordenación en cascada.

Para que quede claro, las capas en cascada no tienen que tener nombre. Nombrarlas sólo aclara mucho las cosas a la hora de establecer un orden para ellas, y también permite añadir estilos a la capa. Aquí tienes algunos ejemplos de uso de capas en cascada sin nombre:

@import url(base.css) layer;

p {margin-top: 1em;}

@layer {
   h1 {color: maroon;}
   body p {margin-top: 0;}
}

En este caso, las reglas importadas de base.css se asignan a una capa sin nombre. Aunque esta capa en realidad no tiene nombre, pensemos en ella como CL1. A continuación, una regla fuera de las capas establece los márgenes superiores del párrafo en 1em. Por último, un bloque de capa sin nombre tiene un par de reglas; pensemos en esta capa como CL2.

Así que ahora tenemos reglas en tres capas: CL1, CL2 y la capa implícita. Y ése es el orden en que se consideran, de modo que en caso de conflicto entre reglas normales, las reglas de la capa implícita por defecto (que es la última en el orden) ganarán sobre las reglas conflictivas de las otras dos capas, y las reglas de CL2 ganarán sobre las reglas conflictivas de CL1.

Al menos, ése es el caso de las reglas de peso normal. Para las reglas de !important, el orden de precedencia se invierte, de modo que las de CL1 ganan a las reglas importantes en conflicto de las otras dos capas, y las reglas importantes de CL2 ganan a las reglas importantes en conflicto de la capa implícita. ¡Extraño pero cierto!

Esta clasificación por orden volverá a surgir dentro de un rato, pero antes introduzcamos la especificidad en la cascada.

Clasificación por Especificidad

Si a un elemento se le aplican declaraciones contradictorias de y todas esas declaraciones tienen el mismo peso explícito, origen, elemento adjunto (o no) y capa en cascada, se ordenan por especificidad. La declaración más específica gana, así:

@layer page {
  p#bright#bright#bright {color: grey;}
}
p#bright {color: silver;}
p {color: black;}
<p id="bright">Well, hello there!</p>

Dadas estas reglas, el texto del párrafo será plateado, como se ilustra en la Figura 4-8. ¿Por qué? Porque la especificidad de p#bright (1,0,1) prevalece sobre la especificidad de p (0,0,1), aunque esta última regla aparezca más tarde en la hoja de estilos. Los estilos de la capa page, aunque tengan el selector más fuerte (3,0,1), ni siquiera se comparan. Sólo se disputan las declaraciones de la capa con precedencia.

css5 0408
Figura 4-8. La mayor especificidad gana a la menor especificidad

Recuerda que esta regla sólo se aplica si las reglas forman parte de la misma capa en cascada. Si no es así, la especificidad no importa: un selector 0,0,1 de la capa implícita ganará a cualquier regla sin importancia de una capa en cascada creada explícitamente, por muy alta que sea la especificidad de esta última.

Clasificar por orden

Por último, si dos reglas tienen exactamente el mismo peso explícito, origen, elemento adjunto, capa en cascada y especificidad, gana la que aparece más tarde en la hoja de estilo, de forma similar a como se ordenan las capas en cascada, de modo que las capas posteriores ganan a las anteriores.

Volvamos a un ejemplo anterior, en el que encontramos las dos reglas siguientes en la hoja de estilo del documento:

body h1 {color: red;}
html h1 {color: blue;}

En este caso, el valor de color para todos los elementos <h1> del documento será blue, no red. Esto se debe a que las dos reglas están empatadas entre sí en cuanto a peso explícito y origen, están en la misma capa en cascada y los selectores tienen igual especificidad, por lo que la última declarada es la ganadora. No importa lo juntos que estén los elementos en el árbol del documento; aunque <body> y <h1> estén más juntos que <html> y <h1>, gana el último. Lo único que importa (cuando el origen, la capa en cascada, la capa y la especificidad son iguales) es el orden en que aparecen las reglas en el CSS.

Entonces, ¿qué ocurre si entran en conflicto reglas de hojas de estilo completamente distintas? Por ejemplo, supongamos lo siguiente

@import url(basic.css);
h1 {color: blue;}

¿Qué ocurre si h1 {color: red;} aparece en basic.css? En este caso, como no hay capas en cascada en juego, todo el contenido de basic.css se trata como si se hubiera pegado en la hoja de estilo en el punto en el que aparece @import. Así, cualquier regla contenida en la hoja de estilo del documento aparece después que las de @import. Si empatan en términos de peso explícito y especificidad, la hoja de estilo del documento contiene la ganadora. Considera losiguiente:

p em {color: purple;}  /* from imported stylesheet */

p em {color: gray;}    /* rule contained within the document */

En este caso, la segunda regla gana a la importada porque es la última especificada, y ambas están en la capa de cascada implícita.

El orden es la razón por la que a menudo se recomienda ordenar los estilos de enlace. La recomendación es que escribas tus estilos de enlace en el orden link, visited, focus, hover, active, o LVFHA, así:

a:link {color: blue;}
a:visited {color: purple;}
a:focus {color: green;}
a:hover {color: red;}
a:active {color: orange;}

Gracias a la información de este capítulo, ahora sabes que la especificidad de todos estos selectores es la misma: 0,1,1. Como todos tienen el mismo peso explícito, origen y especificidad, ganará el último que coincida con un elemento. Un enlace no visitado sobre el que se hace clic o que se activa de otro modo, por ejemplo mediante el teclado, coincide con cuatro de las reglas -:link, :focus, :hover, y :active-, por lo que ganará la última de esas cuatro. Dada la ordenación LVFHA, ganará :active, que es probablemente lo que pretendía el autor.

Supón por un momento que decides ignorar el orden común y ordenar alfabéticamente tus estilos de enlace. El resultado sería el siguiente:

a:active {color: orange;}
a:focus {color: green;}
a:hover {color: red;}
a:link {color: blue;}
a:visited {color: purple;}

Dada esta ordenación, ningún enlace mostraría nunca los estilos :hover, :focus o :active porque las reglas :link y :visited vienen después de las otras tres. Cada enlace debe ser visitado o no visitado, por lo que esos estilos siempre prevalecerán sobre los demás.

Consideremos una variación del orden LVFHA que un autor podría querer utilizar. En este orden, sólo los enlaces no visitados obtendrán un estilo hover; los enlaces visitados no. Tanto los enlaces visitados como los no visitados tendrán un estilo activo:

a:link {color: blue;}
a:hover {color: red;}
a:visited {color: purple;}
a:focus {color: green;}
a:active {color: orange;}

Estos conflictos sólo surgen cuando todos los estados intentan establecer la misma propiedad. Si los estilos de cada estado se dirigen a una propiedad diferente, el orden no importa. En el caso siguiente, los estilos de enlace podrían darse en cualquier orden y seguirían funcionando según lo previsto:

a:link {font-weight: bold;}
a:visited {font-style: italic;}
a:focus {color: green;}
a:hover {color: red;}
a:active {background: yellow;}

También te habrás dado cuenta de que el orden de los estilos :link y :visited no importa. Podrías ordenar los estilos LVFHA o VLFHA sin ningún efecto negativo.

La posibilidad de encadenar pseudoclases elimina todas estas preocupaciones. Las siguientes podrían enumerarse en cualquier orden sin anulaciones, ya que la especificidad de las dos últimas es mayor que la de las dos primeras:

a:link {color: blue;}
a:visited {color: purple;}
a:link:hover {color: red;}
a:visited:hover {color: gray;}

Como cada regla se aplica a un conjunto único de estados de enlace, no entran en conflicto. Por tanto, cambiar su orden no modificará el estilo del documento. Las dos últimas reglas tienen la misma especificidad, pero eso no importa. Un enlace no visitado situado sobre el cursor no se ajustará a la regla relativa a los enlaces visitados situados sobre el cursor, y viceversa. Si añadiéramos estilos de estado activo, el orden empezaría a importar de nuevo. Piensa en esto:

a:link {color: blue;}
a:visited {color: purple;}
a:link:hover {color: red;}
a:visited:hover {color: gray;}
a:link:active {color: orange;}
a:visited:active {color: silver;}

Si los estilos activos se movieran antes que los estilos hover, se ignorarían. De nuevo, esto ocurriría por conflictos de especificidad. Los conflictos podrían evitarse añadiendo más pseudoclases a las cadenas, así

a:link:hover:active {color: orange;}
a:visited:hover:active {color: silver;}

Esto tiene el efecto de aumentar la especificidad de los selectores -ambos tienen un valor de especificidad de 0,3,1-, pero no entran en conflicto porque los estados de selección reales son mutuamente excluyentes. Un enlace no puede ser a la vez un enlace activo pasado por encima visitado y un enlace activo pasado por encima no visitado: sólo coincidirá una de las dos reglas.

Trabajar con sugerencias de presentación no CSS

Un documento puede contener sugerencias de presentación que no sean CSS; por ejemplo, el elemento obsoleto <font>, o los atributos height, width y hidden, todavía muy utilizados. Estas sugerencias de presentación serán anuladas por los estilos del autor o del lector, pero no por los estilos del agente de usuario. En los navegadores modernos, las sugerencias de presentación externas a CSS se tratan como si pertenecieran a la hoja de estilos del agente de usuario.

Resumen

Quizá el aspecto más fundamental de las Hojas de Estilo en Cascada sea la propia cascada: el proceso utilizado para ordenar las declaraciones en conflicto y determinar la presentación final del documento. Una parte integral de este proceso es la especificidad de los selectores y sus declaraciones asociadas, y el mecanismo de herencia.

Get CSS: La Guía Definitiva, 5ª Edición now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.