CSSスコープとは、書いたスタイルが「そのコンポーネントの中だけ」に効くように自動的に制限してくれる仕組みのこと。
学校の制服ルールをイメージするとわかりやすい。A校のルールはA校の生徒にしか適用されない。B校の生徒には関係ない。CSSスコープも同じで、あるコンポーネントに書いたスタイルは、そのコンポーネントの要素にしか効かない。
なぜ必要なのか
CSSはもともと「グローバル」なもので、どこかで h1 { color: red; } と書くと、ページ内のすべての h1 が赤くなってしまう。大規模なサイトになるほど、意図しない場所のスタイルを壊してしまうリスクが高まる。CSSスコープはこの問題を解決する。
Astroでの仕組み
Astroでは <style> タグを使うだけで自動的にスコープが適用される。ビルド時に、Astroがすべてのセレクタに一意の属性(data-astro-cid-xxxxxxxx)を追加する。
/* 書いたコード */
h1 { color: red; }
/* Astroがビルド時に変換するコード */
h1[data-astro-cid-hhnqfkh6] { color: red; }
同時に、そのコンポーネントのHTML要素にも同じ属性が付与される。これにより「この属性を持つ要素だけ」にスタイルが当たる。
詳しくは Astro公式ドキュメント(スタイルとCSS) を参照。
is:global でグローバルに戻す
スコープを外したいときは is:global をつける。
<style is:global>
/* このブロック内はスコープなし(ページ全体に効く) */
.pagination { margin-top: 48px; }
</style>
JavaScriptで動的に生成した要素(ページネーションのボタンなど)は、Astroがビルド時にcid属性を付与できない。そのため、こういった要素のスタイルには is:global を使う必要がある。
落とし穴:*リセットとの詳細度の衝突
is:global と通常の <style> を同じページに混在させると、思わぬ上書きが起きることがある。
よくあるパターンがこれだ。
<style>
/* スコープ付き → 変換後: *[data-astro-cid-xxx] { margin: 0 } → 詳細度 (0,1,0) */
* { margin: 0; padding: 0; }
</style>
<style is:global>
/* グローバル → 変換なし: .pagination { margin-top: 48px } → 詳細度 (0,1,0) */
.pagination { margin-top: 48px; }
</style>
詳細度が同じ場合、CSSはファイル内で後に書かれたルールが優先される。Astroがバンドルを生成するとき、スコープ付きスタイルがグローバルスタイルより後に出力されることがあり、* { margin: 0 } が .pagination { margin-top: 48px } を上書きしてしまう。
display: flex のようなプロパティは * リセットに含まれないので生き残るが、margin は上書きされて 0 になる。DevToolsのComputedタブを見ると margin-top: 0px と表示されていても、CSSファイルには正しく書いてある、という状態になる。
対策
1. is:global 側に !important をつける(手軽な対処)
.pagination { margin-top: 48px !important; }
*2. リセットと動的要素のスタイルを同じブロックに書く(根本的な対処)
同じブロック内であれば .pagination(詳細度 0,1,0)が *(詳細度 0,0,0)より高いので正しく適用される。
3. DevToolsで確認する習慣をつける
スタイルを設定したら、ブラウザのDevToolsでComputedタブを開いて実際の値を確認する。期待と違う値が出ていたら、スコープ問題を疑う。