El scoping por dentro
El mecanismo que hace posible el aislamiento: el atributo data-astro-cid que Astro genera para cada componente, la reescritura de selectores que lo acompaña, el peldaño de especificidad que introduce y su ajuste con scopedStyleStrategy, y por qué los hijos y el HTML dinámico quedan fuera del ámbito.
La marca de ámbito: data-astro-cid
El corazón del sistema es un identificador. Para cada componente .astro que declara estilos, Astro calcula un hash determinista a partir de su identidad —su ruta en el proyecto— y lo usa para construir un atributo con la forma data-astro-cid-<hash>. Durante el build, estampa ese atributo en cada elemento que la plantilla del componente renderiza. El sufijo cid no es casual: significa component id, el sello que dice a qué componente pertenece cada nodo.
<!-- lo que escribiste --><article class="tarjeta"><h2 class="titulo">Hola</h2></article><!-- lo que Astro emite --><article class="tarjeta" data-astro-cid-hbhonbnp> <h2 class="titulo" data-astro-cid-hbhonbnp>Hola</h2></article>Que el hash sea determinista importa más de lo que parece: mientras la identidad del componente no cambie, el atributo es idéntico entre builds. Eso mantiene estables las hojas de estilo, favorece la caché del navegador y evita que un cambio trivial invalide artefactos que no se han tocado. El identificador es corto, opaco y reproducible: justo lo que necesita una marca que va a repetirse en cada nodo.
El mismo hash cumple una segunda función: es el pegamento entre el HTML y el CSS. El atributo que se estampa en los nodos y la condición que se añadirá a los selectores comparten exactamente ese identificador, de modo que ambos lados de la transformación —marcado y estilo— hablan del mismo componente sin ambigüedad posible. Esa es la razón de que la marca deba ser única y estable: si dos componentes compartieran hash, sus ámbitos se fundirían y el aislamiento se rompería.
Selectores reescritos para exigir la marca
El atributo por sí solo no aísla nada; es la mitad de la ecuación. La otra mitad ocurre en el CSS: Astro reescribe cada selector de tu bloque scoped para que, además de lo que ya pedía, exija también la marca de ámbito. Una regla deja de aplicarse a cualquier .titulo del documento y pasa a aplicarse solo a los .titulo que lleven el atributo del componente.
/* lo que escribiste */.titulo { color: #7c3aed; }h2 { margin: 0; }/* lo que Astro compila */.titulo[data-astro-cid-hbhonbnp] { color: #7c3aed; }h2[data-astro-cid-hbhonbnp] { margin: 0; }La reescritura es uniforme: alcanza a los selectores de clase, a los de elemento, a los combinados. Como el atributo solo aparece en los nodos del componente, la conjunción de ambas condiciones —el selector original y la marca— confina la regla al ámbito. Aislamiento por construcción: no se prohíbe que la regla salga, simplemente se le añade una condición que fuera del componente nunca se cumple.
Este detalle explica también por qué el scope no depende del orden de los ficheros ni de la profundidad del árbol: la condición añadida es local a cada componente y no interactúa con las de los demás. Dos componentes con selectores idénticos generan reglas que, aun pareciéndose letra por letra, exigen marcas distintas y por tanto jamás colisionan, independientemente de cuál se cargue antes.
El peldaño de especificidad
Emparejar cada selector con un selector de atributo tiene una consecuencia mensurable: la especificidad sube. Un atributo pesa lo mismo que una clase, así que tu .titulo, que valía 0,0,1,0, pasa a valer 0,0,2,0 una vez compilado. En la práctica esto significa que una regla scoped gana, en igualdad de condiciones, a una regla global equivalente. Casi siempre es lo que quieres —que el estilo local del componente prevalezca—, pero conviene saberlo cuando intentes sobrescribir desde fuera y no lo consigas a la primera.
Astro te deja gobernar ese peldaño con la opción scopedStyleStrategy de la configuración, que elige cómo se ancla la marca en el selector:
// astro.config.mjsimport { defineConfig } from 'astro/config';export default defineConfig({ scopedStyleStrategy: 'where', // por defecto 'attribute'; alternativa 'class'});Ver la diferencia en el CSS emitido despeja cualquier duda. Estas son las tres formas en que la misma regla puede quedar anclada según la estrategia elegida:
/* attribute (por defecto): suma un nivel, como una clase extra */.titulo[data-astro-cid-hbhonbnp] { color: #7c3aed; }/* class: tambien suma un nivel, pero se lee mejor en devtools */.titulo.astro-hbhonbnp { color: #7c3aed; }/* where: cero especificidad, el peso se queda en 0,0,1,0 */.titulo:where([data-astro-cid-hbhonbnp]) { color: #7c3aed; }Con 'where', un .titulo scoped y otro global quedan a la par y decide el orden de aparición: es la estrategia que elegir cuando necesitas que una hoja global pueda ganarle a un componente sin recurrir a trucos de especificidad.
Por qué los hijos y el HTML dinámico quedan fuera
Con el mecanismo a la vista, las fronteras de la lección anterior dejan de ser reglas que memorizar y pasan a ser consecuencias que se deducen. Un componente hijo renderiza sus elementos con su propio data-astro-cid, no con el del padre; por tanto, los selectores reescritos del padre —que exigen la marca del padre— jamás casan con los nodos del hijo. No es una prohibición añadida: es aritmética de atributos.
Lo mismo explica el HTML que aparece en runtime. El contenido que inyectas con set:html, o los nodos que crea tu JavaScript en el cliente, nacen después del build y sin la marca de ámbito estampada. Como no llevan el atributo, ningún selector reescrito los alcanza, y tus reglas scoped los ignoran por completo. Para estilar esos casos hará falta salir del ámbito con las herramientas globales que veremos en la siguiente lección.
Visto en el marcado, el contraste entre padre e hijo es inmediato:
<!-- el padre estampa SU marca en sus nodos --><section data-astro-cid-PADRE> <!-- el hijo estampa la SUYA en los suyos --> <h2 data-astro-cid-HIJO>Titulo del hijo</h2></section>El h2 lleva la marca del hijo, no la del padre; por eso el selector reescrito del padre, que exige data-astro-cid-PADRE, nunca casa con él. La frontera entre componentes no es una regla especial que Astro imponga aparte: es la consecuencia aritmética de que cada componente estampa una marca distinta.