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>
の使い方
<slot>
の詳細仕様と注意点
<slot>
要素は、Web Components(ウェブコンポーネント)の仕組みの一部として登場した、Shadow DOM と呼ばれる仕組みで利用される特別な要素です。通常の HTML 要素のように見えるかもしれませんが、主な役割はカスタム要素(Custom Elements)で作成したコンポーネントの “差し込み口(挿入ポイント)” となることです。
Web Components と Shadow DOM という言葉を初めて聞く方も多いかと思いますが、この仕組みによってDOMツリーの一部をカプセル化しつつ、外部から差し込まれるコンテンツを特定の場所に配置するという強力な機能を実現できます。
<slot>
要素の基礎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>
要素が表示されます。何か要素が渡された場合は、フォールバックコンテンツは非表示になります。
より複雑なコンポーネントを設計するとき、複数の部分に外部から異なるコンテンツを挿入したい場合がよくあります。例えば、ヘッダー用・ボディ用・フッター用など、レイアウトに合わせて挿入ポイントを分けたいケースです。
そんなときに役立つのが「名前付きスロット」です。<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>
の詳細仕様と注意点<slot>
要素は、Light DOM にある要素を再投影(distribution)して表示させる仕組みをもっています。これは単に DOM をコピーするわけではなく、ブラウザによって最適なレンダリングが行われます。
name
属性を変更した場合など中級者以上の方は、動的に要素を追加・削除したときにどのように再投影が走るかを把握しておくと、コンポーネントの振る舞いを予測しやすくなります。
<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);
});
<slot>
の関連複数のスロットを用意する場合、コンポーネント使用者がどういった名前のスロットに何を割り当てればよいかを明確にドキュメント化しておくと、再利用性が高まります。
例 : <slot name="title">
はタイトルとして使う、 <slot name="body">
は本文として使う、など
また、属性やメソッドで制御したほうが分かりやすいようなケースもあります。状況に応じて、スロット経由でコンテンツを差し込むべきか、あるいはカスタム要素のプロパティ・メソッドを使うべきかを検討しましょう。
<slot>
は基本的にどんな要素でも受け取れますが、コンポーネントごとにレイアウトが崩れないよう注意が必要です。また、アクセシビリティ(ARIA 属性など)を設定する際にも、スロット内に渡す要素がコンポーネント全体の動作に影響を与えないか確認しましょう。
this.attachShadow({ mode: 'open' })
のように mode: 'open'
を指定すると、外部から element.shadowRoot
にアクセスできます。一方で mode: 'closed'
にすると、JavaScript から直接 Shadow DOM にアクセスできなくなります。
ただし、<slot>
の挙動自体は open
と closed
のどちらでも機能します。アクセス制御上の理由で 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);
上記の例では、
<slot name="header">
に要素が挿入されないときは No Header Provided
が表示される.highlight
がついている場合は背景色を黄色にする(::slotted(.highlight)
で指定)という形になっています。
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>
には header
・content
・footer
全てに要素を割り当てています。
2 つめの <card-element>
には割り当てていないため、フォールバックコンテンツが表示される挙動を確認できます。
slot
属性の書き忘れ名前付きスロットを使いたいのに、Light DOM 側で slot="..."
属性を書き忘れるとデフォルトスロットに入ってしまい、期待通りに表示されません。
カスタム要素の外部 CSS でスロット内部の要素を直接スタイリングしたい場合、Shadow DOM のカプセル化によって制限されます。::slotted()
を活用するか、必要に応じて外部スタイルを許容する設計にする必要があります。
スロットに正しく要素が挿入されないと、フォールバックコンテンツが表示され続けます。slot
属性の値の誤字やスペルミスが原因であることが多いです。
Shadow DOM 内でイベントが発生したとき、バブリングがどこで止まるかを理解していないと、外部のイベントリスナーが呼び出されない場合があります。
<slot>
自体には多くの場合イベントリスナーを直接つけず、コンテンツ要素(スロット内の子要素)または slotchange
イベントを検討するのがおすすめです。
<slot>
は Shadow DOM の内部に外部(Light DOM)の要素を挿入するための差し込み口です。slotchange
イベントを使うと、スロットに割り当てられる要素の変更を検知できます。<slot>
を使ってコンテンツを挿入してみる::slotted()
を駆使して、必要最低限のスタイルを適用する方法を試してみる<slot>
は一見シンプルですが、Shadow DOM のカプセル化と組み合わさることで非常に強力なコンポーネント化を実現します。初めは理解するのに少し戸惑うかもしれませんが、一度仕組みをマスターすれば、複雑な UI を部品単位でまとめられる柔軟な設計が可能になります。ぜひカスタム要素を活用して、保守性と拡張性の高い Web アプリケーションを構築してみてください。