HTML

The HTML <slot> element is a special element that serves as an “insertion point” within the Shadow DOM for content passed in from the Light DOM.

slot 要素

はじめに

<slot> 要素は、Web Components(ウェブコンポーネント)の仕組みの一部として登場した、Shadow DOM と呼ばれる仕組みで利用される特別な要素です。通常の HTML 要素のように見えるかもしれませんが、主な役割はカスタム要素(Custom Elements)で作成したコンポーネントの “差し込み口(挿入ポイント)” となることです。

Web Components と Shadow DOM という言葉を初めて聞く方も多いかと思いますが、この仕組みによってDOMツリーの一部をカプセル化しつつ、外部から差し込まれるコンテンツを特定の場所に配置するという強力な機能を実現できます。

Shadow DOM と <slot> 要素の基礎

Shadow DOM とは

Shadow DOM は、ある要素の内部に閉じた DOM ツリーを作成できる機能です。

普段扱う DOM(Light DOM)とは分離されており、外部からスタイルやスクリプトの影響を受けにくくなります。

これによって**カプセル化(encapsulation)**が実現され、複雑なページでもコンポーネント単位で管理しやすくなります。

例えば、下記の例のようにカスタム要素 <my-element> を定義し、その内部に Shadow DOM を作ることができます。

HTML

<my-element>
	<!-- この部分は Light DOM -->
</my-element>

実際には JavaScript で以下のように Shadow DOM をアタッチします。

JavaScript

class MyElement extends HTMLElement {
	constructor() {
		super();
		// Shadow Root を開いた状態(open)で作成
		this.attachShadow({ mode: 'open' });
		// ...
	}
}
customElements.define('my-element', MyElement);

すると、this.shadowRoot の配下に独立した DOM ツリーを構築できるようになります。このとき、外部の CSS やスクリプトの干渉を抑えられるのが大きな特長です。

<slot> 要素の役割

Shadow DOM 内では、通常の HTML 要素を配置するほかに、外部(Light DOM)から差し込まれるコンテンツを挿入するポイントが必要になることがあります。そこで登場するのが <slot> 要素です。

<slot> がない場合、カスタム要素の子要素として書かれたコンテンツは Shadow DOM には表示されなくなり、外側に隠蔽されてしまいます。しかし <slot> を使うことで、コンポーネントの設計者が「外部から受け取るコンテンツを、Shadow DOM 内のどこに表示するか」を自由に指定できるようになります。

基本的な <slot> の使い方

もっともシンプルな例

最も基本的なのは、Shadow DOM 内に以下のように <slot> を配置することです。

JavaScript

<!-- my-element.js -->
class MyElement extends HTMLElement {
	constructor() {
		super();
		const shadow = this.attachShadow({ mode: 'open' });
		shadow.innerHTML = `
			<div class="wrapper">
				<!-- ここに外部からの子要素が入る -->
				<slot></slot>
			</div>
		`;
	}
}
customElements.define('my-element', MyElement);

そして HTML ページ側(Light DOM 側)で以下のように <my-element> を使います。

HTML

<my-element>
	<p>これは外部から挿入されるコンテンツです。</p>
	<p>もうひとつの段落です。</p>
</my-element>

このとき、<my-element> に定義された Shadow DOM 内の <slot> は、Light DOM にある <p> 要素2つを内部に挿入して表示してくれます。

フォールバックコンテンツ

<slot> は、外部から挿入される要素がない場合や、意図的に空になっている場合に備えて、フォールバックコンテンツを指定できます。例えば下記のように書きます。

HTML

<slot>
	<p>ここにデフォルトのコンテンツが入ります。</p>
</slot>

外部(Light DOM)に要素が何も渡されなかった場合は、上記で定義した <p> 要素が表示されます。何か要素が渡された場合は、フォールバックコンテンツは非表示になります。

名前付きスロット(Named Slots)

なぜ名前付きスロットが必要か

より複雑なコンポーネントを設計するとき、複数の部分に外部から異なるコンテンツを挿入したい場合がよくあります。例えば、ヘッダー用・ボディ用・フッター用など、レイアウトに合わせて挿入ポイントを分けたいケースです。

そんなときに役立つのが「名前付きスロット」です。<slot> 要素に name 属性を付与することで、コンテンツの差し込み口を明確に識別できます。

名前付きスロットの定義

カスタム要素側(Shadow DOM 内)では以下のように定義します。

HTML

<!-- Named Slots in shadow root -->
<div class="header">
	<slot name="header"></slot>
</div>
<div class="content">
	<slot name="content"></slot>
</div>
<div class="footer">
	<slot name="footer"></slot>
</div>

名前付きスロットへの割り当て

一方、HTML ページ側(Light DOM 側)では、任意の要素に対して slot 属性で名前を指定し、どのスロットに差し込むかを指定します。

HTML

<my-element>
	<h1 slot="header">外部から渡されるヘッダー</h1>
	<p slot="content">ここがメインのコンテンツです。</p>
	<div slot="footer">ここはフッター用の領域です。</div>
</my-element>

このとき、slot="header" と指定された <h1> は Shadow DOM 内の <slot name="header"> の部分へ、slot="content" と書かれた <p><slot name="content"> へ、slot="footer"<slot name="footer"> へそれぞれ差し込まれます。

名前を指定しない(デフォルトスロット)

名前の指定がない要素は、<slot> タグのうち名前の指定がない(<slot><slot name=""> など)の部分に割り当てられます。デフォルトスロットを用意しておけば、「名前がついていないコンテンツがあればここに入る」という形で柔軟に取り扱えます。

<slot> の詳細仕様と注意点

コンテンツの再投影(Slotting)の仕組み

Shadow DOM 内に配置された <slot> 要素は、Light DOM にある要素を再投影(distribution)して表示させる仕組みをもっています。これは単に DOM をコピーするわけではなく、ブラウザによって最適なレンダリングが行われます。
再投影が行われるタイミング

中級者以上の方は、動的に要素を追加・削除したときにどのように再投影が走るかを把握しておくと、コンポーネントの振る舞いを予測しやすくなります。

<slot> とスタイリング(:slotted() 疑似要素)

Shadow DOM では原則としてカプセル化が効くため、Shadow DOM 内のスタイルはスロットに差し込まれた要素(外部要素)を直接スタイリングできません。しかし、特殊な疑似要素 ::slotted() を使用すると、スロットに挿入された要素に限定的なスタイルを当てることができます。

例えば以下のように書くと、スロット経由で渡ってきた <p> 要素にだけ特定のスタイルを当てられます。

CSS

::slotted(p) {
	color: red;
	font-weight: bold;
}

ただし ::slotted() セレクターは :hover などの擬似クラスとの組み合わせや、孫要素への適用などに制約があります。複雑なスタイリングをするにはややコツが必要になるので注意しましょう。

スロットのフォールバックコンテンツと表示のされ方

先述したように <slot> にフォールバックコンテンツを定義すると、Light DOM 側から渡された要素が存在しないときのみそのフォールバックが表示されます。

存在する場合
フォールバックコンテンツは表示されない
存在しない場合
フォールバックコンテンツが表示される

この挙動をうまく利用すると、コンポーネントにデフォルトの見た目を用意しておき、必要に応じて外部から差し込む形にできます。

slotchange イベント

<slot> 要素には、割り当てられた要素が変化したときに発火する slotchange イベントがあります。動的にコンテンツが差し替わる場面を検知したい場合には、このイベントを利用することが可能です。

JavaScript で以下のようにリスナーを登録しておくと便利です。

JavaScript

const mySlot = this.shadowRoot.querySelector('slot[name="header"]');
mySlot.addEventListener('slotchange', (event) => {
	console.log('Header slot content changed!', event);
});

高度なトピック・ベストプラクティス

カスタム要素の API 設計と <slot> の関連

複数のスロットを用意する場合、コンポーネント使用者がどういった名前のスロットに何を割り当てればよいかを明確にドキュメント化しておくと、再利用性が高まります。

例 : <slot name="title"> はタイトルとして使う、 <slot name="body"> は本文として使う、など

また、属性やメソッドで制御したほうが分かりやすいようなケースもあります。状況に応じて、スロット経由でコンテンツを差し込むべきか、あるいはカスタム要素のプロパティ・メソッドを使うべきかを検討しましょう。

スロットに入る要素の制約とアクセシビリティ

<slot> は基本的にどんな要素でも受け取れますが、コンポーネントごとにレイアウトが崩れないよう注意が必要です。また、アクセシビリティ(ARIA 属性など)を設定する際にも、スロット内に渡す要素がコンポーネント全体の動作に影響を与えないか確認しましょう。

オープンとクローズド Shadow DOM

this.attachShadow({ mode: 'open' }) のように mode: 'open' を指定すると、外部から element.shadowRoot にアクセスできます。一方で mode: 'closed' にすると、JavaScript から直接 Shadow DOM にアクセスできなくなります。

ただし、<slot> の挙動自体は openclosed のどちらでも機能します。アクセス制御上の理由で closed にするケースもありますが、開発やデバッグには不便になるため、多くの場合は open が使われます。

再帰的なスロット

Web Components を作り慣れてくると、カスタム要素の中でさらに別のカスタム要素を使い、その内部にまたスロットを…というようなケースもあります。複数の Shadow DOM がネストしているときは、コンポーネント間の境界とスロット名の衝突を避けること、どの要素がどのスロットに挿入されるかを整理しておくことが大切です。

実際のコード例:シンプルなカードコンポーネント

ここでは、実際のコード例として「カード」風のカスタム要素を作ってみましょう。タイトル(ヘッダー)と本文(コンテンツ)、フッターの 3 箇所にスロットを用意し、それぞれ外部から差し込めるようにします。

カードのタイトル

これはカードの本文部分です。

フッター情報

カスタム要素のクラス定義

JavaScript

// card-element.js
class CardElement extends HTMLElement {
	constructor() {
		super();
		this.attachShadow({ mode: 'open' });

		this.shadowRoot.innerHTML = `
			<style>
				.card {
					border: 1px solid #ddd;
					border-radius: 4px;
					padding: 16px;
					margin: 8px;
				}
				.card-header {
					font-weight: bold;
					margin-bottom: 8px;
				}
				::slotted(.highlight) {
					background-color: yellow;
				}
			</style>
			<div class="card">
				<div class="card-header">
					<slot name="header">
						<!-- フォールバックコンテンツ -->
						No Header Provided
					</slot>
				</div>
				<div class="card-content">
					<slot name="content">
						<!-- フォールバックコンテンツ -->
						No Content
					</slot>
				</div>
				<div class="card-footer">
					<slot name="footer">
						<!-- フォールバックコンテンツ -->
						No Footer
					</slot>
				</div>
			</div>
		`;
	}
}

customElements.define('card-element', CardElement);

上記の例では、

という形になっています。

利用側の HTML

HTML

<card-element>
	<span slot="header">カードのタイトル</span>
	<p slot="content" class="highlight">これはカードの本文部分です。</p>
	<small slot="footer">フッター情報</small>
</card-element>

<!-- 何も挿入しなかった場合の例 -->
<card-element></card-element>

最初の <card-element> には headercontentfooter 全てに要素を割り当てています。

2 つめの <card-element> には割り当てていないため、フォールバックコンテンツが表示される挙動を確認できます。

トラブルシューティング:よくある誤解とつまずき

スロットと slot 属性の書き忘れ

名前付きスロットを使いたいのに、Light DOM 側で slot="..." 属性を書き忘れるとデフォルトスロットに入ってしまい、期待通りに表示されません。

スタイルが当たらない / カプセル化の影響

カスタム要素の外部 CSS でスロット内部の要素を直接スタイリングしたい場合、Shadow DOM のカプセル化によって制限されます。::slotted() を活用するか、必要に応じて外部スタイルを許容する設計にする必要があります。

フォールバックコンテンツが消えない

スロットに正しく要素が挿入されないと、フォールバックコンテンツが表示され続けます。slot 属性の値の誤字やスペルミスが原因であることが多いです。

イベントのバブリングや伝搬が予想と違う

Shadow DOM 内でイベントが発生したとき、バブリングがどこで止まるかを理解していないと、外部のイベントリスナーが呼び出されない場合があります。

<slot> 自体には多くの場合イベントリスナーを直接つけず、コンテンツ要素(スロット内の子要素)または slotchange イベントを検討するのがおすすめです。

まとめ

今後のステップ

<slot> は一見シンプルですが、Shadow DOM のカプセル化と組み合わさることで非常に強力なコンポーネント化を実現します。初めは理解するのに少し戸惑うかもしれませんが、一度仕組みをマスターすれば、複雑な UI を部品単位でまとめられる柔軟な設計が可能になります。ぜひカスタム要素を活用して、保守性と拡張性の高い Web アプリケーションを構築してみてください。