Saltar al contenido

Estilos scoped por defecto

El bloque de estilos de un componente .astro y por qué sus reglas no se filtran a otros componentes: el scoping automático, la encapsulación en tiempo de build sin Shadow DOM, el uso de preprocesadores y CSS moderno, y el fin de las colisiones de la cascada global.

Un bloque de estilos que no se derrama

Cualquier componente .astro puede incluir uno o varios bloques <style> junto a su plantilla. Lo singular es lo que Astro hace con ellos: en lugar de emitir esas reglas al ámbito global, las acota al componente que las declara. Dos componentes pueden nombrar una clase .titulo con reglas opuestas y convivir sin interferirse.

ASTRO
---// Tarjeta.astro---<article class="tarjeta">  <h2 class="titulo">Un título</h2></article><style>  .tarjeta { border: 1px solid #ccc; border-radius: 8px; }  .titulo { color: #7c3aed; font-size: 1.25rem; }</style>

La clase .titulo de este fichero no afecta a ningún .titulo de otro componente, ni al revés. No has escrito prefijos, ni convenciones tipo BEM, ni un sistema de nombres defensivo: el aislamiento es la conducta por defecto, no algo que debas activar. El mismo nombre corto y legible —.titulo, .item, .activo— deja de ser un riesgo y vuelve a ser lo que siempre debió ser: descriptivo y local.

Imagina un segundo componente, Aviso.astro, que también declara un .titulo con reglas opuestas. Colocados ambos en la misma página, cada .titulo respeta la suya: el de la tarjeta sigue violeta, el del aviso sigue rojo.

ASTRO
---// Aviso.astro---<p class="titulo">Atención</p><style>  .titulo { color: #dc2626; font-weight: 700; }</style>

No hay ganador de la cascada porque, en la práctica, nunca compiten: viven en ámbitos separados que Astro mantiene estancos. Sin scoping, quien se cargara al final ganaría la partida y el otro .titulo quedaría a merced del orden de importación; con scoping, el resultado es determinista y no depende de qué fichero se procesó primero.

Encapsulación en build, no en runtime

Aquí está la sutileza que conviene interiorizar. El Shadow DOM de los Web Components también aísla estilos, pero lo hace en el navegador, en tiempo de ejecución, montando un árbol aparte con sus propias reglas de herencia. Astro no usa nada de eso. Su encapsulación ocurre durante el build: transforma tu CSS y tu HTML antes de que lleguen al cliente, de modo que el navegador recibe CSS plano y ordinario que aplica con la cascada de siempre.

La consecuencia es doble. Primero, no hay coste en cliente: ni JavaScript, ni un runtime de estilos, ni un árbol sombra que el navegador deba mantener. Segundo, no heredas las asperezas del Shadow DOM —la dificultad de estilar desde fuera, la herencia interrumpida en los límites—, porque tus estilos siguen siendo CSS normal que vive en el mismo documento y participa de la misma cascada.

CSS moderno y preprocesadores dentro del bloque

El aislamiento no te encierra en un CSS pobre. El bloque acepta el atributo lang para delegar en un preprocesador —Sass, SCSS, Less o Stylus—, siempre que instales su paquete; Astro lo integra en el pipeline de Vite sin configuración adicional. Y lo importante: el scoping se aplica después de compilar el preprocesador, así que anidar en SCSS sigue produciendo reglas acotadas al componente.

ASTRO
<style lang="scss">  .tarjeta {    border: 1px solid #ccc;    .titulo { color: #7c3aed; }    &:hover { border-color: #7c3aed; }  }</style>

Si prefieres no añadir dependencias, el CSS nativo de hoy ya cubre gran parte de lo que antes justificaba un preprocesador: el anidamiento nativo, las capas de cascada y las funciones de color modernas funcionan tal cual dentro del bloque, y quedan igual de acotadas. El preprocesador pasa a ser una elección de comodidad, no una necesidad estructural.

Incluso una animación queda encapsulada. Al declarar un @keyframes dentro del bloque, Astro renombra el identificador de la animación por debajo para que no colisione con el de otro componente que, por casualidad, usara el mismo nombre:

ASTRO
<style>  .tarjeta { animation: aparecer 0.4s ease-out; }  @keyframes aparecer {    from { opacity: 0; transform: translateY(8px); }    to { opacity: 1; transform: translateY(0); }  }</style>

Dos componentes pueden definir ambos una animación aparecer con curvas distintas y ninguno pisará al otro: el aislamiento cubre también el espacio de nombres de las animaciones, no solo el de los selectores.

El límite del ámbito

El scope cubre los elementos que el propio componente escribe en su plantilla, y solo esos. No desciende a los componentes hijos que anides dentro: los elementos internos de un hijo pertenecen a su ámbito, no al del padre. Del mismo modo, el HTML que inyectes en tiempo de ejecución —por ejemplo con set:html— no lleva las marcas de ámbito, así que las reglas scoped no lo alcanzarán. Ambas son fronteras deliberadas, coherentes con la idea central: cada componente responde de su propio CSS y de nada más.

Esta demarcación no es una limitación accidental, sino el contrato que hace fiable el sistema. Saber exactamente hasta dónde llega una regla —y dónde deja de llegar— es lo que te permite escribir estilos sin miedo, porque el radio de cada cambio es predecible de antemano.

ASTRO
---// Padre.astroimport Hijo from './Hijo.astro';---<section class="marco">  <Hijo /></section><style>  .marco { padding: 2rem; }  /* esta regla NO alcanza los elementos internos de Hijo */  h2 { color: hotpink; }</style>

El h2 que renderice Hijo conservará su propio color, no el rosa del padre, porque pertenece a otro ámbito. En la próxima lección abriremos la máquina para ver cómo se materializa esa frontera; aquí basta con confiar en que existe y en que Astro la garantiza por ti.