Selectores de CSS, cascada y especificidad: qué regla gana

Escribir una regla de CSS es fácil; lo difícil es saber por qué no se aplica. Casi siempre la respuesta está aquí: o el selector no elige el elemento que crees, o otra regla le está ganando. En esta lección verás los selectores que se usan de verdad y cómo decide el navegador qué regla gana. Cada «¿de qué color sale?» se comprobó en Chrome, Firefox y Safari.

El selector decide a qué se aplica

En la lección 1 escribiste reglas como h1 { color: navy; }, que pintan todos los h1. En una página real casi nunca quieres «todos»: quieres los enlaces del menú, el primer párrafo, la tarjeta destacada. Para eso están los selectores.

  • pDe tipo: todos los elementos p.
  • .avisoDe clase: los elementos con class="aviso". Un elemento puede tener varias clases separadas por espacios: class="aviso grande".
  • #portadaDe id: el elemento con id="portada". Un id no se repite en la página.
  • [type="email"]De atributo: los elementos con ese atributo y ese valor.
  • *Universal: todos los elementos.
  • h1, h2Grupo: la misma regla para varios selectores, separados por comas.
<h2>Ofertas</h2>
<p>Precios de esta semana.</p>
<p class="aviso">Solo en tienda física.</p>
<p class="aviso grande">¡Último día!</p>
<input type="email" placeholder="tu@correo.mx">
p { color: #33475b; }
.aviso { color: #b91c1c; }
.grande { font-size: 1.4rem; font-weight: 700; }
[type="email"] { border: 2px solid #1e64af; }

Usa sobre todo clases. Los selectores de id tienen tanta fuerza (lo verás en la especificidad) que luego cuesta cambiarlos, y los de tipo solos son demasiado generales salvo para los estilos base de la página.

Combinadores: según dónde está

Dos selectores pueden combinarse para elegir elementos según su posición en el árbol:

  • nav aDescendiente (un espacio): los a que están dentro de un nav, a cualquier profundidad.
  • ul > liHijo directo: los li cuyo padre inmediato es un ul.
  • h2 + pHermano siguiente: el p que va justo después de un h2.
  • h2 ~ pHermanos posteriores: todos los p que vienen después de un h2 dentro del mismo padre.
<nav><a href="#">Inicio</a> <a href="#">Blog</a></nav>
<p>Un <a href="#">enlace normal</a>.</p>
<h2>Noticias</h2>
<p>El primer párrafo tras el título.</p>
<p>El segundo párrafo.</p>
nav a { font-weight: 700; color: #0f766e; }
h2 + p { font-size: 1.15rem; }

Pseudoclases: según su estado o su lugar

Las pseudoclases empiezan por dos puntos y eligen elementos según algo que no está escrito en el HTML: si el ratón está encima, si es el primero de su grupo, si un campo es válido.

  • :hoverEl ratón está encima.
  • :focus-visibleTiene el foco del teclado. Nunca quites el contorno del foco sin poner otro: es lo que guía a quien navega con Tab.
  • :first-child, :last-childEs el primero o el último hijo de su padre.
  • :nth-child(2n)Los hijos pares; odd para los impares, 3 para el tercero.
  • :not(.x)Los que no cumplen el selector de dentro.
  • :has(img)Los que contienen algo que cumple el selector: el «selector padre» que se pidió durante veinte años.
<ul class="lista">
  <li>Lunes: cerrado</li>
  <li>Martes a viernes: 9 a 18 h</li>
  <li>Sábado: 10 a 14 h</li>
  <li>Domingo: cerrado</li>
</ul>
<div class="tarjeta"><p>Tarjeta sin imagen</p></div>
<div class="tarjeta"><img src="https://decodigo.com/wp-content/uploads/2026/10/decodigo_placa_raspberry-pi-zero-2-w.jpg" alt="Raspberry Pi Zero 2 W" width="160" height="105"><p>Tarjeta con imagen</p></div>
.lista li:nth-child(odd) { background: #eef3f9; }
.lista li:hover { background: #1e64af; color: white; }
.tarjeta { border: 2px solid #c9d6e4; padding: 0.5rem; margin-top: 0.5rem; }
.tarjeta:has(img) { border-color: #1e64af; }

Pasa el ratón por la lista. Y fíjate en las tarjetas: la regla .tarjeta:has(img) pone el borde azul solo en la que contiene una imagen, sin tocar el HTML. :has() funciona en Safari desde la 15.4, en Chrome y Edge desde la 105 y en Firefox desde la 121.

Los pseudoelementos, con dos pares de dos puntos, eligen una parte de un elemento: ::first-letter (la primera letra), ::marker (la viñeta de una lista), ::placeholder, o crean contenido decorativo antes o después con ::before y ::after.

El error típico: un espacio de más

<p class="nota">Nota importante</p>
p .nota { color: #b91c1c; }

El texto no sale rojo (sale negro). Con el espacio, p .nota significa «un elemento con clase nota dentro de un p», y aquí la clase está en el propio párrafo. Sin espacio, p.nota, significa «un p que tiene la clase nota». Un espacio cambia por completo el selector.

La cascada: cuando dos reglas chocan

Es normal que varias reglas quieran cambiar la misma propiedad del mismo elemento. CSS no da un error: decide cuál gana con un procedimiento fijo, la cascada (por eso se llama Cascading Style Sheets).

La cascada decide en cuatro pasos: origen e importancia, capas, especificidad y, si todo empata, el orden en el códigoCuando dos reglas chocan1Origen e importancia!important gana a lo normal2Capas (@layer)fuera de capa gana a dentro3Especificidadids > clases > etiquetas4Orden en el códigola última ganaSi un paso decide, los siguientes no cuentan.
Los cuatro pasos de la cascada, en orden. Esquema: decodigo.com.

El último paso es el más fácil: con la misma fuerza, gana la regla que aparece después.

<p id="prueba">¿De qué color soy?</p>
p { color: #1e64af; }
p { color: #b91c1c; }

Sale rojo: las dos reglas tienen el mismo selector y gana la segunda. Esto también vale entre archivos: una hoja enlazada después le gana a otra enlazada antes.

La especificidad

Antes del orden se mira la especificidad: cuánto de concreto es el selector. Se cuenta en tres columnas: cuántos ids tiene, cuántas clases, atributos y pseudoclases, y cuántas etiquetas y pseudoelementos.

Especificidad de varios selectores en tres columnas, ids, clases y etiquetas: p es 0,0,1; .nota 0,1,0; p.nota 0,1,1; nav a:hover 0,1,2; #menu a 1,0,1; [type=email] 0,1,0Especificidad: tres columnasidsclasesetiquetasp001.nota010p.nota011nav a:hover012#menu a101[type=email]010Se compara columna por columna, de izquierdaa derecha: un id gana a cualquier número de clases.
Cada selector, con su especificidad en tres columnas. Esquema: decodigo.com.
<p id="prueba" class="nota">¿Y ahora?</p>
#prueba { color: #0f766e; }
p.nota { color: #1e64af; }
.nota { color: #b91c1c; }

Sale verde, aunque la regla del id va primera: #prueba es (1,0,0) y le gana a p.nota, que es (0,1,1). Las columnas se comparan de izquierda a derecha y la primera diferencia decide, así que un solo id le gana a cualquier cantidad de clases. Por eso conviene usar clases para el estilo: todas pesan parecido y el orden decide lo demás.

Dos pseudoclases tienen una especificidad especial, y sirven justo para controlarla:

<p id="prueba" class="a">Texto</p>
:where(#prueba) { color: #b91c1c; }
p { color: #1e64af; }

Sale azul. :where() siempre cuenta cero, aunque tenga un id dentro, así que un simple p le gana. Es ideal para estilos base que cualquiera debe poder cambiar. :is() hace lo mismo para elegir, pero cuenta como el selector más fuerte de su lista: :is(#prueba, p) pesa como un id.

El atributo style y !important

El atributo style escrito en el HTML gana a cualquier selector de las hojas de estilo. Y una declaración con !important sube al primer paso de la cascada: gana a todo lo que no lo tenga, sin importar la especificidad.

<p id="prueba" style="color: #1e64af">Texto</p>
p { color: #b91c1c !important; }

Sale rojo: el !important de un simple p le gana incluso al atributo style. Parece útil, pero es una trampa: para ganarle a un !important hace falta otro !important más específico, y así hasta que nadie entiende la hoja. Resérvalo para casos muy concretos, como una clase de utilidad que debe ganar siempre (.oculto { display: none !important; }).

Capas: @layer

Las capas, de 2022, son la forma moderna de ordenar estilos de distintas procedencias (un reinicio, una biblioteca, los tuyos) sin pelear con la especificidad. Se declaran en orden y la capa posterior gana a la anterior, sea cual sea la especificidad. Lo que no está en ninguna capa gana a todas.

<p id="prueba">Texto</p>
@layer base, componentes;

@layer componentes {
  p { color: #1e64af; }
}

@layer base {
  #prueba { color: #b91c1c; }
}

Sale azul: componentes se declaró después de base en la primera línea, así que su humilde p le gana al #prueba de base. Funciona en Chrome 99, Firefox 97 y Safari 15.4 en adelante.

Con !important el orden de las capas se invierte: un !important en la primera capa gana al de las siguientes. Lo verás con más detalle en las rarezas de CSS.

La herencia

Si ninguna regla dice nada de una propiedad, algunas se heredan del padre: las de texto, como color, font-family, font-size o line-height. Por eso basta con poner la letra en body para toda la página. Las de caja, como border, margin, padding o background, no se heredan: sería un caos que cada elemento repitiera el borde de su padre.

<article>
  <h2>Título</h2>
  <p>Un párrafo con un <a href="#">enlace</a> y un <button>botón</button>.</p>
</article>
article {
  color: #0f766e;
  font-family: Georgia, serif;
  border: 2px solid #c9d6e4;
  padding: 0.5rem 1rem;
}

El título y el párrafo heredan el color verde y la letra Georgia; ninguno hereda el borde. Pero fíjate en dos excepciones que confunden a todo el mundo: el enlace no sale verde (el navegador le pone su propio color, azul o morado, y eso cuenta como una regla) y el botón no sale en Georgia (los controles de formulario traen su letra propia). Para que hereden, se les pide explícitamente:

a { color: inherit; }
button, input, select, textarea { font: inherit; }

Esas dos líneas están en casi cualquier hoja de estilos moderna.

Para ver la cascada en acción, abre las herramientas del navegador (F12), elige un elemento y mira el panel de estilos: las declaraciones que perdieron aparecen tachadas, con el archivo y la línea de la que vienen. La pestaña «Calculado» (Computed) muestra el valor final de cada propiedad y qué regla lo puso.

Retos

Reto 1. Escribe un selector para cada caso: (a) los enlaces dentro del pie de página; (b) el primer elemento de cada lista; (c) los campos input obligatorios; (d) las tarjetas que no tienen la clase destacada.

Ver la solución
footer a { }
li:first-child { }
input[required] { }   /* o input:required */
.tarjeta:not(.destacada) { }

Reto 2. ¿De qué color sale el texto? HTML: <div class="caja"><p class="texto">Hola</p></div>. CSS: .caja p { color: blue; } y, después, .texto { color: red; }.

Ver la solución
<div class="caja"><p id="prueba" class="texto">Hola</p></div>
.caja p { color: #1e64af; }
.texto { color: #b91c1c; }

Sale azul. .caja p es (0,1,1) y .texto es (0,1,0): gana el primero por tener una etiqueta más, aunque esté antes en el código.

Reto 3. Haz que los enlaces de un menú cambien de fondo al pasar el ratón y que se vea un contorno grueso cuando lleguen con el teclado, sin quitar el del ratón.

Ver la solución
<nav class="menu">
  <a href="#">Inicio</a>
  <a href="#">Precios</a>
  <a href="#">Contacto</a>
</nav>
.menu a {
  display: inline-block;
  padding: 0.4rem 0.8rem;
  border-radius: 6px;
  color: inherit;
  text-decoration: none;
}
.menu a:hover {
  background: #eef3f9;
}
.menu a:focus-visible {
  outline: 3px solid #1e64af;
  outline-offset: 2px;
}

Cada fragmento de HTML de esta lección pasó por el Nu Html Checker 26.11.4, el validador oficial del W3C, y cada resultado se pintó en Chromium, Firefox y WebKit (los motores de Chrome, Firefox y Safari); las cifras del texto se midieron en los tres. ¿Quieres probar algo? Usa el editor de HTML y CSS. Logotipos de HTML5 (W3C) y CSS3 (Rudloff) en la imagen de la página: Wikimedia Commons, CC BY 3.0.