技術 約3分で読めます

AIはAstroのスコープドCSSがJavaScriptで生成した要素に適用されない事を忘れる

冒頭でいきなり結論

こんな話Next/Nuxt使ってる人皆知ってると思うがAIくんは毎度同じことかますんで、 Claude.md/Gemini.md/Agents.mdに書かせてちゃんと頭入れとけって言っとくと面倒がないって話。 以下は何の話かわからない人向け。

問題

Astroコンポーネント内の <style> タグはデフォルトでスコープドCSSとしてコンパイルされる。これはコンポーネント間のスタイル汚染を防ぐ便利な機能だが、JavaScriptで動的に生成したDOM要素には適用されないという落とし穴がある。

なぜ適用されないのか

Astroのスコープドは、ビルド時にHTMLとCSSを解析し、一意のクラス(data-astro-cid-xxxxxのような属性)を付与することで実現している。

<!-- ビルド前 -->
<div class="card">Hello</div>
<style>
  .card { background: blue; }
</style>

<!-- ビルド後(イメージ) -->
<div class="card" data-astro-cid-abc123>Hello</div>
<style>
  .card[data-astro-cid-abc123] { background: blue; }
</style>

JavaScriptで動的に生成した要素にはこの属性が付与されないため、セレクタがマッチせずスタイルが適用されない。

<div id="container"></div>

<style>
  .dynamic-item { color: red; } /* 適用されない! */
</style>

<script>
  const container = document.getElementById('container');
  const item = document.createElement('div');
  item.className = 'dynamic-item'; // data-astro-cid-xxx がない
  item.textContent = 'Dynamic content';
  container.appendChild(item);
</script>

解決策

1. is:global を使う

<style is:global> を使うとスコープドではなくグローバルCSSとして出力される。

<style is:global>
  .dynamic-item { color: red; } /* 適用される */
</style>

ただしグローバルなので、他のページやコンポーネントにも影響する可能性がある。命名規則(BEM、プレフィックス等)で衝突を避けるか、十分にユニークなクラス名を使う。

2. :global() セレクタを使う

一部のセレクタだけをグローバルにしたい場合は :global() 擬似クラスを使う。

<style>
  /* スコープド(静的要素用) */
  .container { padding: 1rem; }

  /* グローバル(動的要素用) */
  :global(.dynamic-item) { color: red; }
</style>

3. インラインスタイルを使う

JavaScript側で直接スタイルを設定する。

const item = document.createElement('div');
item.style.color = 'red';
item.style.fontSize = '14px';

シンプルなスタイルなら有効だが、複雑になると管理しづらい。

4. Tailwind CSSを使う

Tailwind CSSはユーティリティクラスとしてグローバルに定義されているため、動的要素でも問題なく使える。

const item = document.createElement('div');
item.className = 'text-red-500 text-sm font-bold';

「全部グローバルCSSにすればいいじゃん」

そう思うかもしれないが、スコープドCSSには以下のメリットがある:

  • 名前衝突を気にしなくていい: .card.buttonみたいな汎用的な名前を各コンポーネントで自由に使える
  • デッドコード削除: 使われなくなったコンポーネントのCSSは自動的に消える。グローバルCSSは手動で掃除が必要
  • 影響範囲が明確: このコンポーネントのスタイルはこのファイル内で完結、という安心感

動的生成がないページではスコープドのままでいい。動的生成があるページだけ is:global を使えばいい。全部グローバルにするのは「問題を回避する」ではなく「メリットを捨てる」になる。

まとめ

方法適用範囲用途
<style is:global>ページ全体動的生成が多いページ
:global(.class)特定クラスのみ一部だけ動的な場合
インラインスタイル要素単位シンプルなスタイル
Tailwind CSSグローバルプロジェクト全体で使用時

AIにはちゃんとやれって教えておけって話。