Web ComponentsとTailwind CSSの併用が難しい理由を少し深掘りする

はじめに
こんにちは!
KINTOテクノロジーズのプロジェクト推進グループでWebエンジニアをしている亀山です。
フロントエンドを勉強中です。
モダンなWeb開発においてはコンポーネント指向が主流となっています。UIを再利用可能な部品に分割することで、開発効率や保守性が向上します。Web ComponentsとTailwind CSSは、どちらもコンポーネント指向のフロントエンド開発を支援する強力なツールです。
Web Componentsは、標準仕様に基づいてカプセル化された再利用可能なカスタム要素を作成できる近年注目を集めている技術です。一方、Tailwind CSSは、ユーティリティファーストのアプローチで高速なUIスタイリングを実現するCSSフレームワークです。最近だとTailwind CSSもパフォーマンスが向上したv4が登場しておりアップデートも活発です。
一見すると、これらの技術は相性が良いように思えるかもしれません。「コンポーネントごとにカプセル化されたマークアップとロジック(Web Components)に、ユーティリティクラスで手軽にスタイルを当てる(Tailwind CSS)」という組み合わせは魅力的、、、だと思っていました。
いざ開発を始めると、どうしてもうまくいかない。調べていくとWeb Componentsの根幹をなすShadow DOMと、Tailwind CSSのスタイリングメカニズムには、お互いの根本的な思想が衝突していることがわかりました。本記事では、特にShadow DOMの観点から、両者がなぜ相性が悪いのか、そしてなぜ併用するべきではないのか私が勉強したことをまとめていきます。
Web Componentsについて
Shadow DOMとは何ぞや
まずWeb Componentsは主に以下の3つの技術から構成されてます。
- Custom Elements: 独自のHTML要素(例:
<my-button>
)を定義するAPI - Shadow DOM: コンポーネント内部のDOMツリーとスタイルを、外部から隔離(カプセル化)する技術
- HTML Templates: 再利用可能なマークアップの断片を保持するための
<template>
要素と<slot>
要素
この中でも、Tailwind CSSとの相性の悪さに直結するのがShadow DOMの存在です。
Shadow DOMを要素にアタッチすると、その要素はShadow Hostとなり、内部に隠されたDOMツリー(Shadow Tree)を持ちます。Shadow Tree内の要素に対するスタイルは、原則としてShadow Treeの外部(メインドキュメントや親のShadow Tree)からは影響を受けません。逆に、Shadow Treeの外部で定義されたスタイルも、原則としてShadow Tree内部には適用されません。
これは、コンポーネントのスタイルが外部のCSSルールに汚染されたり、コンポーネント内部のスタイルが外部に漏れ出たりするのを防ぐ、強力なスタイルのカプセル化機能を提供します。これにより、異なるCSS設計手法が混在する環境でも、コンポーネントの見た目が予期せず崩れるといった心配がなくなります。
下記に併用した場合のソースコードの例を記載します。
class MyStyledComponent extends HTMLElement {
constructor() {
super();
// Shadow DOMをアタッチ(openモードで外部からアクセス可能に)
const shadowRoot = this.attachShadow({ mode: 'open' });
// Shadow DOM内部のHTML構造
const template = document.createElement('template');
template.innerHTML = `
<div class="container mx-auto p-4 bg-blue-200"> // Tailwindクラスを使用
<p class="text-xl font-bold text-gray-800">Hello from Shadow DOM!</p> // Tailwindクラスを使用
<button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Click me
</button>
</div>
`;
shadowRoot.appendChild(template.content.cloneNode(true));
// ★ ここが問題 ★ Shadow DOM内部にスタイルを適用するには...?
// 外部のスタイルシートは原則届かない
}
}
customElements.define('my-styled-component', MyStyledComponent);
上記の例のように、MyStyledComponent
の Shadow DOM 内で <div class="container mx-auto p-4 bg-blue-200">
のようなTailwindクラスを使用しても、デフォルトではこれらのスタイルは適用されません。
Tailwind CSSについて
ユーティリティファーストとグローバルCSS
Tailwind CSSは、flex
, pt-4
, text-blue-500
といった低レベルなユーティリティクラスをHTMLに直接記述することで、高速にUIを構築するアプローチを採用しています。
ビルドプロセスにおいて、TailwindはプロジェクトのHTML、JavaScript、TypeScriptファイルなどをスキャンし、使用されているユーティリティクラスに対応するCSSルールを生成します。生成されたCSSは、通常、グローバルな単一のスタイルシートとして出力され、HTMLドキュメントの<head>
などに読み込まれます。
例えば、HTMLに <div class="flex pt-4">
があれば、Tailwindは以下のようなCSSルールを生成し、グローバルスタイルシートに含めます。
/* Tailwindによって生成されるCSS(の例) */
.flex {
display: flex;
}
.pt-4 {
padding-top: 1rem;
}
このTailwindのスタイリングメカニズムにおける重要な点は、CSSルールがグローバルスコープで定義されるという点です。
2つの絶望的な相性の悪さ
Shadow DOMのカプセル化 vs. Tailwindのグローバルスタイル
ここで問題の核心部分です。
- Shadow DOM は、内部の要素に外部のスタイルが適用されないようにカプセル化する
- Tailwind CSS は、使用されているユーティリティクラスに対応するCSSルールをグローバルスコープに生成する
この二つは根本的に矛盾します。Tailwindがグローバルに生成した.flex { display: flex; }
のようなCSSルールは、Shadow DOMの境界を越えてShadow Tree内の要素に到達しないのです。
先ほどのTypeScriptの例で、<div class="container mx-auto p-4 bg-blue-200">
にTailwindのスタイルが当たらないのは、これらのクラスに対応するCSSルールがShadow DOMの外部(メインドキュメントのグローバルスコープ)に存在し、Shadow DOMがそのルールの適用をブロックしているからです。
Tailwind CSS v4について補足: Tailwind CSS v4では、新しいエンジンによるパフォーマンス向上などが謳われていますが、基本的なスタイリングのメカニズム(プロジェクトファイルをスキャンしてユーティリティクラスに対応するCSSをグローバルに生成する)という点では変わりません。したがって、v4を使用してもShadow DOMとの相性の悪さは解消されません。
どうにかできんのか?(解決策はあるのか?)
この問題を解決するために、色々調べていると、この衝突の回避策はあるにはあるが、どれもWeb ComponentsやTailwind CSSのメリットを損なう、あるいは実装コストが非常に高いものになり、根本的な解決策は見つかりませんでした。苦し紛れなものですが回避策をいくつか紹介します。
ビルドしたTailwindのCSSをShadow DOM内にコピー&ペーストする
各Web ComponentのShadow DOM内に、そのコンポーネントで使用しているTailwindクラスに対応するCSSルールを手動、あるいはビルドツールで抽出して<style>
タグとして埋め込む方法です。
デメリット:
- 非常に手間がかかり、メンテナンス性が低い
- コンポーネントごとに重複したCSSを持つことになり、ファイルサイズが増大する
- TailwindのJITコンパイル(使っているクラスだけを生成する)のメリットが活かせない
- Tailwindの運用ワークフロー(設定ファイル、プラグインなど)と乖離する
Shadow DOMを使用しない
Web ComponentsでShadow DOMを使わず、Light DOMに要素を配置する方法です。この場合、要素はメインドキュメントのDOMツリーの一部と見なされるため、グローバルなTailwindスタイルが適用されます。
デメリット:
- Web Componentsの最大のメリットである「スタイルのカプセル化」が失われ、外部のCSSがコンポーネントに影響を与えたり、コンポーネントのスタイルが外部に漏れ出たりする可能性が生じてコンポーネントの独立性が損なわれる
これらのアプローチを見てもわかるように、Shadow DOMによる強力なカプセル化と、グローバルスタイルシートに依存するTailwind CSSは、根本的に思想が異なるため、無理に併用しようとするとどちらかの技術のメリットを大きく損なうことになります。
結論:Web ComponentsとTailwind CSSは併用するべきではない
これまで見てきたように、Web Components(特にShadow DOMを利用する場合)とTailwind CSSの併用は、両者のメリットを打ち消し合ってしまうため、基本的には避けるべきです。
その理由は、2つの技術が持つスタイリングの根本的な思想・仕組みが衝突するからです。
- Web Components (Shadow DOM) は、コンポーネントのスタイルを外部から完全に**隔離(カプセル化)**することを目的としている
- 一方、Tailwind CSS は、ユーティリティクラスに対応するCSSをグローバルなスタイルシートとして生成し、ページ全体に適用することを前提としている
このため、Tailwindが生成した便利なユーティリティクラスのスタイルは、Shadow DOMの強固な壁を越えることができず、コンポーネント内部には適用されません。
回避策は存在するものの、いずれもコンポーネントの独立性を犠牲にしたり、開発の複雑さを増大させたりと、本末転倒な結果を招きがちです。それぞれの技術の長所を最大限に活かすためには、併用しないという選択が賢明と言えるでしょう。
今回の記事が、Web ComponentsとTailwind CSSの併用を検討されている方の参考になれば幸いです。
関連記事 | Related Posts
We are hiring!
【フロントエンドエンジニア(コンテンツ開発)】新車サブスク開発G/東京・大阪・福岡
新車サブスク開発グループについてTOYOTAのクルマのサブスクリプションサービスである『 KINTO ONE 』のWebサイトの開発、運用をしています。業務内容トヨタグループの金融、モビリティサービスの内製開発組織である同社にて、自社サービスである、クルマのサブスクリプションサービス『KINTO ONE』のWebサイトコンテンツの開発・運用業務を担っていただきます。
【フロントエンドエンジニア】新車サブスク開発G/東京・大阪・福岡
新車サブスク開発グループについてTOYOTAのクルマのサブスクリプションサービスである『 KINTO ONE 』のWebサイトの開発、運用をしています。業務内容トヨタグループの金融、モビリティサービスの内製開発組織である同社にて、自社サービスである、TOYOTAのクルマのサブスクリプションサービス『KINTO ONE』のWebサイトの開発、運用を行っていただきます。