Saltar al contenido

CSS, de a poco

Curso de CSS

Cascada moderna

Lección 23 de 37 · 5 min

Cuando dos reglas le ponen un valor distinto a la misma propiedad, la cascada decide cuál gana. Con capas, nesting y @scope podés ordenar tus estilos sin subir la especificidad a la fuerza.

El orden de la cascada

El navegador junta todas las declaraciones que aplican a un elemento y las compara por pasos. Solo pasa al siguiente si hay empate:

  1. Origen e importancia!importantTus estilos le ganan a los del navegador, y una declaración con !important le gana a una normal.
  2. Contextoshadow DOMCon Shadow DOM, los estilos del componente y los de la página se comparan acá.
  3. Atributo stylestyle="…"Lo escrito en el atributo le gana a cualquier selector.
  4. Capas@layerGana la capa declarada después, y lo que no está en ninguna capa le gana a todas.
  5. Especificidad(1, 0, 1)La que se explica en Selectores y cascada.
  6. Proximidad@scopeEntre dos reglas de @scope, gana la de la raíz más cercana.
  7. Orden de apariciónA igualdad de todo lo anterior, gana la última.

Fijate dónde está la especificidad: después de las capas. Una regla con un ID en una capa temprana pierde contra una clase en una capa posterior. Eso es lo que hace útiles a las capas.

Capas con @layer

Una línea con @layer y una lista de nombres fija el orden de las capas: la primera es la más débil. Después cada bloque @layer nombre { … } mete reglas en su capa, en cualquier orden y en cualquier archivo.

styles.css
@layer reset, base, componentes;
@layer componentes {
.boton { background: royalblue; }
}
@layer base {
button { background: gray; } /* pierde: base va antes */
}
/* también se puede importar un archivo entero en una capa */
@import url("vendor.css") layer(vendor);

El orden lo decide la primera vez que aparece cada nombre. Por eso conviene declarar la lista arriba de todo: si un bloque aparece antes que la lista, su capa queda primera.

Cambiá el orden, sumá una regla sin capa y probá el !important. El panel muestra por qué pierde cada regla:

@layer base, componentesgana: @layer componentes

El enlace “Ver combinaciones” se pinta de azul: gana la regla de @layer componentes.

Línea B · Aviso de servicio

Hasta el 30 de noviembre, los trenes no paran en Pasteur–AMIA por obras en los andenes.

Ver combinaciones

Estilos de a.enlace

  1. @layer baseespecificidad (1, 0, 1)#aviso acolor: var(--gris)pierde: su capa se declaró antes
  2. @layer componentesespecificidad (0, 1, 0).enlacecolor: var(--azul)gana
orden
!important
aviso.css
@layer base, componentes;
@layer base {
#aviso a {
color: var(--gris);
}
}
@layer componentes {
.enlace {
color: var(--azul);
}
}

Un @layer { … } sin nombre crea una capa anónima: ocupa su lugar en el orden, pero no se le pueden sumar reglas después. Las capas también se anidan: @layer framework { @layer base { … } } crea framework.base, y adentro de framework sus subcapas se ordenan igual que afuera.

Las capas son Baseline desde marzo de 2022 (Chrome y Edge 99, Firefox 97, Safari 15.4) y ampliamente disponibles desde septiembre de 2024.

!important da vuelta las capas

Con !important el orden de las capas se invierte: gana la capa declarada antes, y lo que no tiene capa queda último. Parece raro, pero tiene sentido: una capa de reset puede marcar algo como importante y ninguna capa posterior lo pisa sin querer.

Dos pilas con las mismas capas, de la más fuerte arriba a la más débil abajo. En normal gana sin capa, después componentes, base y reset. Con !important el orden se invierte: gana reset, después base, componentes y sin capa queda último.
styles.css
@layer reset, componentes;
@layer reset {
[hidden] { display: none !important; } /* gana: reset va antes */
}
@layer componentes {
.menu { display: flex !important; }
}

Lo mismo pasa con los orígenes: el !important del navegador le gana al tuyo. Como regla práctica, ordená con capas y dejá !important para casos así, donde algo nunca debería pisarse.

Nesting nativo

CSS ya permite escribir reglas adentro de otras, como en Sass. & representa al selector de afuera; si no lo ponés, la regla anidada se lee como descendiente.

tarjeta.css
.tarjeta {
padding: 16px;
h2 { font-size: 18px; } /* .tarjeta h2 */
&:hover { background: #f5f5f5; } /* .tarjeta:hover */
.oscuro & { color: white; } /* .oscuro .tarjeta */
@media (width >= 40rem) {
padding: 24px; /* .tarjeta, en pantallas anchas */
}
}

Dos diferencias con Sass. No concatena texto: &__titulo no genera .tarjeta__titulo. Y el selector de afuera se trata como si estuviera dentro de :is(), así que toma la especificidad del más alto de la lista:

menu.css
#menu, .menu {
a { color: inherit; }
}
/* es :is(#menu, .menu) a → (1, 0, 1), también para .menu a */

El nesting es Baseline desde diciembre de 2023 (Chrome y Edge 120, Firefox 117, Safari 17.2) y ampliamente disponible desde junio de 2026. Chrome 112 a 119 ya lo soportaba, pero sin reglas anidadas que empiecen con una etiqueta, como h2 sin &.

@scope

@scope limita reglas a una parte del documento. La raíz marca dónde empieza y el límite, opcional, dónde termina: lo que está dentro del límite (y el límite mismo) queda afuera.

receta.css
@scope (.receta) to (.nota) {
:scope { border: 1px solid; } /* la raíz misma */
p, li { font-family: Georgia, serif; }
}

Desmarcá el límite: la nota del editor también pasa a la tipografía con serifa.

@scope (.receta) to (.nota)midiendo…

Cocina · Recetas al horno

Fainá

Masa fina de harina de garbanzos, al horno bien fuerte hasta que el borde se dore.

  • 250 g de harina de garbanzos
  • 750 ml de agua
  • 4 cucharadas de aceite de oliva
  • Sal y pimienta negra

Nota del editor: en Génova se llama farinata y se come sola, no arriba de la pizza.

receta.css
@scope (.receta) to (.nota) {
p, li {
font-family: Georgia, serif;
}
}

La raíz no suma especificidad: p adentro de @scope (.receta) sigue valiendo (0, 0, 1). :scope cuenta como una pseudoclase, (0, 1, 0).

Cuando dos scopes chocan con la misma especificidad, gana el de la raíz más cercana al elemento, aunque esté antes en el código. Eso resuelve los temas anidados:

temas.css
@scope (.claro) { a { color: navy; } }
@scope (.oscuro) { a { color: skyblue; } }
/* <div class="oscuro"> … <div class="claro"> <a> …
el enlace es navy: .claro está más cerca */

Un <style> con @scope sin raíz se aplica al elemento que lo contiene. @scope está en Chrome y Edge desde la 118, Safari desde la 17.4 y Firefox recién desde la 146: es Baseline desde marzo de 2026. Si tenés que soportar navegadores más viejos, las reglas adentro de un @scope que el navegador no entiende se ignoran enteras.

En Tailwind

Tailwind v4 ya usa capas: declara @layer theme, base, components, utilities. Por eso una clase utilitaria le gana a tu CSS de components aunque tenga menos especificidad, y tu CSS sin capa le gana a todo Tailwind.

CSSTailwind
estilos de base o componentes@layer components { … }
una utilidad propia@utility nombre { … }
color: … !importanttext-red-600!
.clase a { … }[&_a]:underline
hijos · descendientes*:text-sm · **:text-sm
@scope[&_p:not(.nota_*)]:font-serif
app.css
@import "tailwindcss";
@layer components {
.aviso a { color: var(--color-blue-700); }
}
@utility enlace-sutil {
color: inherit;
text-decoration-color: var(--color-neutral-300);
}
aviso.html
<!-- utilities va después de components: gana text-green-700 -->
<p class="aviso">
… <a class="text-green-700 hover:enlace-sutil" href="/combinaciones">Ver combinaciones</a>
</p>