Web Componentsのslotとは?

slotは👇

👉 「コンポーネントの中身を外から差し込む」ものです。

基本:デフォルトslotの使い方

コンポーネント側

<slot></slot>

使用側

<my-box>
  <p>中身</p>
</my-box>

👉 コンポーネント側に定義された <slot> の位置に、使用側の<p>中身</p>がそのまま入る。


差し込まれる位置はどのように決められるか?

👉 「どこに差し込むか」は、コンポーネント側が決めます。

コンポーネント側

<div class="card">
  <header>
    <slot name="header"></slot>
  </header>

  <main>
    <slot></slot>
  </main>

  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

使用側

<my-card>
  <h1 slot="header">タイトル</h1>
  <p>本文</p>
  <button slot="footer">OK</button>
</my-card>

👇 この場合、slotによる再配置により、こう表示されます。

<div class="card">
  <header>
    <h1>タイトル</h1>
  </header>

  <main>
    <p>本文</p>
  </main>

  <footer>
    <button>OK</button>
  </footer>
</div>

ルール(重要)

👉 つまり、差し込み先には、下記のルールが適用されます。

書き方 行き先
slot="header"
slot="footer"
指定なし (デフォルト)

同じslotが指定されている場合はどうなるか?

<my-card>
  <h1 slot="header">タイトル1</h1>
  <h2 slot="header">タイトル2</h2>
</my-card>

👉 結果

<header>
  <h1>タイトル1</h1>
  <h2>タイトル2</h2>
</header>

👉 同じslot名は全部入ります。(順番も維持)


slotが無い場合はどうなるか?

<my-card>
  <p>本文1</p>
  <p>本文2</p>
</my-card>

👉 結果

<main>
  <p>本文1</p>
  <p>本文2</p>
</main>

👉 全部デフォルトslotに入る。


対応するslotが無いとどうなるか?

<my-card>
  <p slot="sidebar">サイドバー</p>
</my-card>

👉 コンポーネントに

<slot name="sidebar"></slot>

が無い場合

👉 表示されません(消えます)


デフォルトslotの注意点

<slot></slot>

👉 デフォルトslotは基本1つしか機能しません。

仮に、2つ定義しても

<slot></slot>
<slot></slot>

👉 最初の1つしか使われない。


フォールバック(デフォルト表示)

コンポーネント側

<div class="card">
  <header>
    <slot name="header">タイトル未設定</slot>
  </header>

  <main>
    <slot></slot>
  </main>

  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

利用側

<my-card>
  <p>本文だけ</p>
</my-card>

👉 結果

<div class="card">
  <header>タイトル未設定</header>

  <main>本文だけ</main>

  <footer></footer>
</div>

👉 中身が無いときの保険。使用側で指定されなかった場合のデフォルト値を指定できます。


slotの本質(ここが一番大事)

👉 注意が必要なのは、slotでは、HTMLがコピーされるのではなく「配置が変わる」だけなので、差し込まれたタグの実態は、使用側のDOMに存在したままとなります。

よくある誤解

👉 「slotは中にコピーされる」

❌ 違います

正解

👉 再配置(projection)

つまり:

  • 元のDOMは外にある
  • 表示だけ中に移動する

その影響(実務ポイント)

✔ イベントは普通に拾える

button.addEventListener("click", ...)

👉 問題なし

✔ shadowRootから直接取れない

this.shadowRoot.querySelector("p") // ❌

✔ 正しい取得方法

const slot = this.shadowRoot.querySelector("slot");
const nodes = slot.assignedNodes();


実務での設計パターン

✔ 良い設計

<app-card>
  <span slot="header">タイトル</span>
  <p>本文</p>
</app-card>

👉 汎用的で再利用できる

❌ 悪い設計

this.shadowRoot.innerHTML = `
  <h1>${title}</h1>
  <p>${content}</p>
`;

👉 固定すぎて使い回せない


属性との組み合わせ(最重要)

<user-card name="山田">
  <button slot="footer">フォロー</button>
</user-card>

👉 役割分担

役割 手段
データ 属性
UI slot

👉 これができると設計が一気に綺麗になります。


まとめ

slotのルール

  • slot="名前" → 対応するslotへ
  • 指定なし → デフォルトslotへ
  • 同じ名前 → 複数入る
  • slotが無い → 消える

slotの本質

👉 「構造(コンポーネント)と中身(利用側)を分離する仕組み」