CSSのスタイルはデフォルトでグローバルにカスケードするため、あるコンポーネントのセレクタが意図せず別のコンポーネントに影響を与えることがあります。開発者は長年にわたり、命名規則、ビルド時ツール、DOM分離などでこの問題に対処してきました。そして今、CSS @scopeアットルールが登場し、近接性ベースのカスケード制御という新しい選択肢を提供しています。
@scopeアットルール
CSS Scoping Module Level 1で導入された@scopeは、スタイルを指定したルート要素とその子孫に制限します。命名規則とは異なり、ブラウザによって強制される点が決定的に異なります。
@scope (.card) {
.title { font-size: 1.25rem; font-weight: 600; }
.body { padding: 1rem; }
}
最大の革新は**スコープ近接性(scope proximity)**です。要素に近いスコープで定義されたスタイルは、詳細度に関係なく優先されます。スコープリミットを使用すると、スタイルが適用されるサブツリーをさらに制限できます。
@scope (.card) to (.card__footer) {
.link { color: blue; }
}
これにより、カードの中に別のカードがある場合でも、内側のカードは外側のスコープスタイルの影響を受けません。@scopeはChrome 118+、Safari 17.4+、Firefox 128+でサポートされています。
Shadow DOMとCSS Modules
Shadow DOMは最も強力なカプセル化を提供します。スタイル、マークアップ、動作がシャドウルート内で完全に分離されます。
class MyCard extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: "open" });
shadow.innerHTML = `
<style>.title { font-size: 1.25rem; }</style>
<div class="title">Hello</div>
`;
}
}
customElements.define("my-card", MyCard);
CSS Modulesはビルド時にローカルスコープのクラス名を生成し、ランタイムコストゼロで衝突を防止します。
/* card.module.css */
.title { font-size: 1.25rem; }
.body { padding: 1rem; }
import styles from "./card.module.css";
function Card() {
return <div className={styles.card}>
<h2 className={styles.title}>Title</h2>
</div>;
}
ビルドツールが.titleを.card_title_3kl4sのような一意な名前に変換します。TypeScript統合やcomposesによるクラス合成も可能ですが、要素セレクタはスコープされないという制限があります。
BEMと実践的戦略
BEM(Block Element Modifier)は.block__element--modifierという命名規則で衝突を防止します。ツールは不要ですが、真のカプセル化はなく、大規模コードベースではカスケード漏洩が発生します。
| 戦略 | カプセル化 | ランタイムコスト | ビルド手順 | 最適な用途 |
|---|---|---|---|---|
@scope | カスケードレベル | なし | 不要 | モダンCSSのコンポーネント |
| CSS Modules | クラスレベル | なし | 必要 | フレームワークベースのSPA |
| Shadow DOM | 完全なDOM分離 | インスタンスごと | 不要 | 分散ウィジェット、デザインシステム |
| BEM | 慣習のみ | なし | 不要 | ビルド手順のないプロジェクト |
最も実践的なのはハイブリッドアプローチです。コンポーネントファイルにはCSS Modulesを使用し、その中で@scopeでカスケードを管理します。分散ウィジェットライブラリにはShadow DOM、グローバルユーティリティにはBEMを採用します。@scopeはCSSにブラウザ強制のスコープをもたらすパラダイムシフトであり、日常的なコンポーネントスタイリングのカスケード管理を劇的にシンプルにします。
