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.
Durante décadas, escribir CSS a escala fue una lucha contra la cascada: una clase .tarjeta definida en un rincón del proyecto podía teñir un componente en el otro extremo sin que nadie lo hubiera pedido. Astro rompe esa herencia con una decisión tajante: todo bloque <style> que escribas dentro de un componente .astro está, por defecto, encapsulado. Sus reglas alcanzan a ese componente y a nadie más.
- Escribir un
<style>dentro de un componente.astroy observar que sus reglas quedan confinadas a él. - Comprender por qué el scoping por defecto elimina las colisiones de nombres de clase entre componentes.
- Distinguir la encapsulación en tiempo de build de Astro del aislamiento en runtime del Shadow DOM.
- Usar preprocesadores y CSS moderno dentro del bloque sin perder la garantía de aislamiento.
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.
---
// 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.
---
// 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.
Astro no deja tus reglas incrustadas en mitad del HTML. Extrae el contenido de cada <style>, lo procesa y lo agrupa en hojas que enlaza en la cabecera del documento. Si un componente se repite cien veces en una página, sus estilos se emiten una sola vez. La encapsulación no penaliza el peso: pagas por regla declarada, no por instancia renderizada.
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.
CSS global tradicional
Un único espacio de nombres. Cualquier regla puede alcanzar cualquier elemento. La protección depende de la disciplina del equipo.
Scoped de Astro
Cada componente es su propio ámbito. Las reglas se reescriben en build para no salir de él. Sin convenciones, sin colisiones.
Shadow DOM
Aislamiento real en runtime, con árbol y límites propios. Potente, pero costoso de estilar y ajeno al modelo estático de Astro.
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.
flowchart TD SRC[componente .astro con bloque style] --> BUILD[compilador de Astro] BUILD --> REW[reglas reescritas y acotadas] BUILD --> HTML[HTML con marcas de ambito] REW --> SHEET[hoja de estilos agrupada] HTML --> DOM[navegador] SHEET --> DOM DOM --> OK[cada regla solo toca su componente] style SRC fill:#89b4fa,color:#11111b style OK fill:#a6e3a1,color:#11111b
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.
<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:
<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.
Encapsular no es recortar. Selectores combinados, pseudoclases, pseudoelementos, consultas de contenedor, animaciones con @keyframes: todo es válido dentro de un <style> scoped. Astro solo añade una condición de pertenencia a cada selector; no toca su poder expresivo. Escribes el mismo CSS de siempre y recibes, gratis, la garantía de que no saldrá del componente.
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.
---
// Padre.astro
import 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.
Como ningún nombre puede escaparse, no necesitas prefijos ni sufijos defensivos. Reserva la energía que gastabas en inventar nombres únicos y úsala en que sean claros: .cabecera, .cuerpo, .pie. Dentro del componente, la legibilidad manda; fuera, el scoping ya te protege. Es un cambio pequeño de hábito con un gran efecto sobre cuánto se lee tu CSS.
El problema histórico del CSS nunca fue su sintaxis, sino su ámbito: una hoja de estilos es, por diseño, un espacio de nombres global donde toda regla compite con todas las demás por cada elemento del documento. La cascada, la especificidad y el orden de aparición son los árbitros de esa competencia, y gestionarlos a escala engendró metodologías enteras —BEM, OOCSS, CSS Modules— cuyo único propósito era simular la localidad que el lenguaje no ofrecía. Astro no simula esa localidad: la fabrica en el build. Al acotar cada <style> a su componente, convierte el ámbito global en una suma de ámbitos locales, y con ello traslada el CSS del terreno de la disciplina colectiva al de la garantía mecánica. La diferencia es epistémica. Con convenciones, la ausencia de colisiones es una esperanza que depende de que nadie del equipo se equivoque; con scoping, es una propiedad del sistema que se cumple aunque todos se equivoquen. Y lo logra sin pedir nada a cambio en runtime, porque la transformación sucede antes de que el navegador vea una sola regla: lo que llega al cliente es CSS corriente, indistinguible del que escribirías a mano, solo que imposible de derramar. Ahí reside la elegancia. Astro no inventa un modelo de estilos exótico ni te obliga a aprender un aislamiento ajeno como el del Shadow DOM; toma el CSS que ya conoces y le devuelve, en tiempo de compilación, la única cosa que siempre le faltó: saber dónde termina.
- Crea dos componentes distintos y da a ambos una clase
.titulocon colores diferentes; úsalos en la misma página y confirma que cada uno conserva el suyo. - Inspecciona el HTML renderizado en el navegador y localiza las marcas que Astro añade a los elementos de cada componente.
- Anida un tercer componente hijo dentro de uno de los anteriores y verifica que las reglas del padre no tiñen los elementos internos del hijo.
- Reescribe un bloque con
lang="scss"usando anidamiento y comprueba, en el CSS final, que las reglas resultantes siguen acotadas al componente.