こんにちは。フロントエンドの現場で日々CSSの魔改造と格闘している君なら、一度はこんな絶望を味わったことがあるはずだ。
「Web Components使ってカプセル化されたのはいいけど、中のボタンのホバー色ひとつ変えられないじゃん……!」
そう、Shadow DOMという名の「完全なる城壁」の前に、僕たちの強力なCSSセレクタは無力化される。外から `.my-component button` なんて叩いても、カプセル化の壁に阻まれてビクともしない。CSS Variablesで変数を流し込むという逃げ道もあるが、コンポーネント側の設計者が変数を定義してくれていなければ、それも手詰まりだ。
だが、安心してほしい。今日のテーマである `::part` 擬似要素 と `part` 属性を使いこなせば、コンポーネントのカプセル化を完全に破壊することなく、「ここだけは外からスタイリングさせてやるよ」という公式の抜け穴(API)を優雅に彫り込むことができる。
今日は、実務で明日から使えるこの強力な武器について、ブラウザの裏側の動きも含めて徹底的に解説しよう。
—
なぜShadow DOMは僕たちのCSSを拒絶するのか?
まず敵を知ることから始めよう。Shadow DOMの本質は「カプセル化」だ。
ReactのCSS ModulesやVueのScoped CSSは、ビルド時にクラス名にハッシュを付与して衝突を防いでいる「疑似的なカプセル化」に過ぎない。しかし、Web ComponentsのShadow DOMは、ブラウザのレンダリングエンジンレベルでDOMツリーを隔離する。
外部のCSSが内部に漏れ出さないし、内部のCSSも外に漏れ出さない。これは巨大なアプリケーションを開発する上では最高のエクスプロキシだが、デザインシステムを構築する側からすると「融通の利かない頑固ジジイ」に見える瞬間がある。
「いやいや、子孫セレクタで中の要素を直撃させてよ!」と思うかもしれないが、それを許すとコンポーネントの内部構造(カプセル化された実装詳細)に外部のスタイルが依存してしまい、コンポーネントのバージョンアップ時にアプリ全体が崩壊するという、昔ながらの悪夢が再来する。
そこでW3Cの仕様策定者たちが生み出したのが、「カプセル化の壁に、あらかじめ設計者が許可した『小窓』を開ける」というアプローチ、それが `part` 属性と `::part` 擬似要素だ。
—
`part` と `::part` の基本メカニズム
仕組みは驚くほどシンプルだ。
1. コンポーネントの内部(Shadow Tree内)で、外部からスタイルを当てさせたい要素に `part=”任意の名前”` を付与する。
2. 外部のCSS(ホスト側の文書)から、カスタム要素名に対して `::part(任意の名前)` を使ってスタイルを記述する。
ブラウザはこれをどう処理しているか?
ブラウザのレンダリングエンジンは、`::part` を見つけると、「Shadow DOMの境界をまたぐ特例」として、指定されたパーツにのみ外部からのスタイルルールを適用する。コンポーネントの他の部分は相変わらず鉄壁のガードに守られたままだ。
言葉だけではピンとこないだろう。実務でそのまま使える、カード型コンポーネントのサンプルコードを見てみよう。
—
実践!コピペで動くコード例
以下のコードをそのままブラウザ(ChromeやSafariなど、現代のモダンブラウザならどこでも動く)のHTMLファイルに貼り付けてみてほしい。
このコードをブラウザで開くと、`user-card` というカプセル化されたコンポーネントの中にある `