Saltar al contenido

CSS, de a poco

Curso de CSS

Selectores y cascada

Lección 07 de 37 · 4 min

Una regla de CSS tiene dos partes: el selector dice a qué elementos apunta y las declaraciones dicen qué cambian. Cuando varias reglas apuntan al mismo elemento, la cascada decide cuál gana.

Tipos de selectores

El selector es lo que va antes de las llaves. Estos son los más comunes:

  1. TipopTodos los elementos con esa etiqueta.
  2. Clase.avisoLos elementos que tienen esa clase en su atributo class. Un elemento puede tener varias.
  3. ID#notaEl elemento con ese id, que tiene que ser único en la página.
  4. Atributo[type="email"]Los elementos que tienen ese atributo, o ese valor.
  5. Pseudo-clase:hover · :first-childUn estado o una posición del elemento.

Escritos juntos, sin espacio, forman un selector compuesto: p.aviso pide un p que además tenga la clase aviso. Los combinadores relacionan elementos entre sí: un espacio elige descendientes a cualquier profundidad, y > solo hijos directos.

Un article con un p propio y un div que tiene otro p adentro. article p elige los dos párrafos, a cualquier profundidad; article > p elige solo el hijo directo, y el p del div queda afuera.
styles.css
p.aviso { } /* un p con la clase aviso */
article p { } /* un p en cualquier lugar dentro de un article */
article > p { } /* un p que es hijo directo de un article */
a:hover { } /* un enlace con el puntero encima */
input[type="email"] { } /* un input de tipo email */

Especificidad

Cuando dos reglas le dan valores distintos a la misma propiedad, gana la de mayor especificidad (specificity). La especificidad se escribe como tres números (a, b, c):

  • aCuántos IDs tiene el selector.
  • bCuántas clases, atributos y pseudo-clases.
  • cCuántos tipos y pseudo-elementos. El selector universal * y los combinadores no suman.

Se compara columna por columna, de izquierda a derecha, y decide la primera que difiere. Por eso (1, 0, 0) le gana a (0, 12, 0): no es un número decimal, y ninguna cantidad de clases alcanza a un ID. Si las tres columnas empatan, gana la regla que aparece después.

Activá y desactivá reglas y mirá cuál gana. Los controles están en el orden en que aparecen en la hoja de estilos; la tabla las ordena como el navegador y marca en naranja la columna que decide.

<p id="nota" class="aviso destacado">gana p.aviso
Guías · Marruecos

Dos días en las dunas de Merzouga

Desde Erfoud son dos horas de ruta hasta el borde de las dunas. Conviene llegar antes de las cinco.

El último tramo se hace en camello: llevá agua y protector solar.

Estilos, en el orden del navegador
selectorabccolor
p.aviso011ganarebeccapurple
.aviso010pisadasienna
p001pisadaroyalblue
:where(#nota)000pisadadimgray

decide la columna c: 1 > 0

Reglas, en orden de aparición
selectores.css
:where(#nota) { color: dimgray; } /* (0, 0, 0) */
p { color: royalblue; } /* (0, 0, 1) */
p.aviso { color: rebeccapurple; } /* (0, 1, 1), gana */
.aviso { color: sienna; } /* (0, 1, 0) */
selectores.html
<!-- gana p.aviso (0, 1, 1): en Tailwind ese color va como una utilidad en el elemento -->
<p class="text-[rebeccapurple]">El último tramo se hace en camello: llevá agua y protector solar.</p>

:where() tiene especificidad cero, lleve lo que lleve adentro: :where(#nota) pierde incluso contra p. En cambio :is() y :not() toman la especificidad de su argumento más específico: :is(#nota, .aviso) vale (1, 0, 0).

El orden de la cascada

La especificidad no es lo primero que mira el navegador. La cascada compara las declaraciones en este orden, y solo pasa al siguiente criterio si hay empate:

  1. Origen e importanciaEn declaraciones normales, tus estilos le ganan a los del navegador. Una declaración con !important le gana a una normal.
  2. Capas@layerGana la capa declarada después, y lo que no está en ninguna capa le gana a todas.
  3. EspecificidadLa de la sección anterior. Recién acá entra en juego.
  4. Orden de apariciónA igual especificidad, gana la última.

Es una versión simplificada: también cuentan el Shadow DOM y el atributo style, que le gana a cualquier selector. Evitá !important: para pisarlo hace falta otro !important. Mantené la especificidad baja con :where() y ordená el código en capas:

capas.css
@layer base, componentes;
@layer componentes {
.boton { color: royalblue; } /* (0, 1, 0), gana: su capa va después */
}
@layer base {
#app .boton { color: gray; } /* (1, 1, 0), pero en una capa anterior */
:where(nav) a { color: inherit; } /* (0, 0, 1): fácil de pisar */
}

El orden de las capas lo fija la primera línea, no el orden en que aparecen los bloques.

En Tailwind

Tailwind es utility-first: cada clase hace una sola cosa y casi todas pesan lo mismo, (0, 1, 0). Como no escribís selectores que compitan entre sí, la especificidad deja de ser un problema. Los selectores siguen existiendo, pero como variantes que se anteponen a la clase. Y en v4 el ! de !important va al final: text-[crimson]!.

CSSTailwind
a:hoverhover:text-sky-700
:focus-visiblefocus-visible:outline-2
li:first-child · li:nth-child(3)first:font-bold · nth-3:font-bold
label:has(input:checked)has-checked:bg-sky-50
.card:hover .titulogroup → group-hover:underline
input:checked ~ labelpeer → peer-checked:font-bold
article > p[&>p]:mt-4
recordarme.html
<label class="group flex items-center gap-2 has-checked:bg-sky-50">
<input type="checkbox" class="peer" />
<span class="peer-checked:font-bold group-hover:underline">Recordarme</span>
</label>
<ul class="[&>li]:py-1">
<li class="first:font-bold nth-3:text-gray-500">Uno</li>
<li>Dos</li>
<li>Tres</li>
</ul>