Kodokon kodokon.com

Capas de cascada (@layer) y @scope

Domina los dos nuevos criterios de la cascada - las capas y la proximidad de ámbito - para estructurar tus estilos sin guerras de especificidad.

10 min · 3 preguntas

Abrir esta lección en Kodokon

La cascada resuelve las declaraciones en conflicto usando criterios ordenados: origen e importancia, luego el contexto, luego los estilos del atributo style, luego las capas (@layer), luego la especificidad, luego el orden de aparición. El punto crucial: las capas se evalúan antes que la especificidad. Una regla .button en una capa de mayor prioridad vence, por tanto, a #nav div.menu a.button en una capa de menor prioridad - su especificidad de (1,2,2) no cuenta para nada frente al orden de las capas. Eso es lo que hace que @layer sea tan potente para estructurar un sistema de diseño: la prioridad se declara una vez, de forma estructural, en lugar de tener que pelearla selector a selector.

CSS
@layer reset, base, components, utilities;

@layer components {
  .button { background: rebeccapurple; }
}

@layer base {
  button { background: gray; }
}
La declaración inicial fija el orden: components vence a base, sin importar el orden de los bloques en el archivo.

Dos sutilezas de la especificación que absolutamente necesitas conocer. Una: los estilos fuera de cualquier capa vencen a los estilos con capa (para las declaraciones normales) - forman una capa implícita colocada en último lugar. Tus sobrescrituras locales siempre vencen, por tanto, a la biblioteca con capas. Dos: con !important, el orden se invierte por completo. Las primeras capas declaradas vencen, y un !important dentro de una capa vence a un !important fuera de cualquier capa. Una declaración !important en tu capa reset se vuelve casi imbatible.

CSS
@layer base, theme;

@layer base {
  a { color: black !important; }
}

@layer theme {
  a { color: purple !important; }
}
Con !important, el orden se invierte: black vence porque base se declara primero.

@scope delimita un conjunto de reglas entre una raíz de ámbito y un límite inferior opcional: @scope (.card) to (.card__content) apunta a los elementos que son descendientes de .card pero están fuera de .card__content - el famoso donut scope, ideal para dar estilo al marco de un componente sin filtrarse en su contenido inyectado. Dentro, :scope se refiere a la raíz misma. Y @scope introduce un criterio de cascada totalmente nuevo: la proximidad. A igual especificidad, vence la regla cuya raíz de ámbito está más cerca del elemento - algo que ni BEM ni los combinadores pudieron nunca expresar.

CSS
@scope (.card) to (.card__content) {
  :scope {
    padding: 1rem;
    border: 1px solid oklch(0.85 0.02 260);
  }
  img { border-radius: 8px; }
}
Los elementos img del marco de .card quedan apuntados; los inyectados en .card__content quedan fuera de ámbito.

Prueba de conocimientos

Comprueba que has retenido los puntos clave de esta lección.

  1. Con @layer a, b; y dos declaraciones normales de igual especificidad, una en a y otra en b, ¿cuál se aplica?
    • La de la capa a, declarada primero
    • La de la capa b, porque vence la última capa declarada
    • La que aparece más tarde en el archivo, ignorando las capas
  2. Una declaración normal fuera de cualquier capa se enfrenta a una declaración normal colocada en una capa. ¿Resultado?
    • La declaración con capa siempre vence
    • La declaración sin capa vence
    • Decide la especificidad del selector
    • El navegador reporta un conflicto
  3. En @scope (.card) to (.card__content), ¿a qué elementos pueden apuntar las reglas internas?
    • A todos los descendientes de .card sin excepción
    • A los descendientes de .card situados fuera de .card__content (donut scope)
    • Solo a .card__content y sus hijos