コンポーネント・UI

Web Components

ウェブコンポーネントは、一連のさまざまな技術です。これにより、再利用可能なカスタム要素を作成し、その機能を他のコードから分離してウェブアプリケーションで利用できるようにします。

3 つの主要技術

Web Components は 3 つの主要技術で構成されています。これらを組み合わせることで、カプセル化された機能を持つ多目的なカスタム要素を作成し、コードの衝突を恐れることなくどこでも再利用できます。

Custom Elements

独自の HTML 要素を JavaScript で定義し、ブラウザに登録する仕組み

カスタム要素とその動作を定義するための JavaScript API です。定義したカスタム要素は、通常の HTML 要素と同じようにページ内で使用できます。要素の名前にはハイフン(-)を含む必要があります(例: my-element)。

my-element user-card app-header
myelement myElement header

カスタム要素の名前にはハイフン(-)が必須です。これにより、ブラウザの標準要素との名前衝突を防ぎます。

基本的な使い方
class MyElement extends HTMLElement {
  connectedCallback() {
    this.innerHTML = '<p>Hello, Web Components!</p>';
  }
}
customElements.define('my-element', MyElement);
<!-- HTML で使う -->
<my-element></my-element>
2 種類のカスタム要素
自律型
<my-element>

HTMLElement を継承。完全に新しい要素を定義。

カスタマイズ組み込み型
<p is="my-p">

既存の HTML 要素を拡張。アクセシビリティを自動的に継承。

ライフサイクルコールバック

カスタム要素はブラウザによって管理される 4 つのライフサイクルコールバックを持っています。これらのコールバックを使って、要素の生成・DOM への接続・属性の変更・DOM からの削除に応じた処理を定義できます。

constructor() 要素を生成
connectedCallback() DOM に追加
attributeChangedCallback() 属性が変更
disconnectedCallback() DOM から削除
1
constructor() 要素が生成された時

要素の初期状態をセットアップする。super() の呼び出しが必須。Shadow DOM の作成はここで行う。

✓ DO
  • super() を呼ぶ
  • attachShadow()
  • 状態の初期化
✗ DON'T
  • 属性の読み取り
  • 子要素へのアクセス
  • innerHTML の設定
2
connectedCallback() DOM に追加された時

要素が文書の DOM ツリーに接続された時に呼ばれる。レンダリング、イベントリスナーの登録、データ取得はここで行う。

connectedCallback() {
  this.shadowRoot.innerHTML = `
    <style>:host { display: block; }</style>
    <h2>${this.getAttribute('title')}</h2>
    <slot></slot>
  `;
  this.addEventListener('click', this._onClick);
}
3
attributeChangedCallback(name, oldVal, newVal) 監視対象の属性が変更された時

static observedAttributes で指定した属性が追加・削除・変更された時に呼ばれる。UI の更新をリアクティブに行える。

static observedAttributes = ['name', 'theme'];

attributeChangedCallback(name, oldVal, newVal) {
  switch (name) {
    case 'name':
      this.shadowRoot.querySelector('.name').textContent = newVal;
      break;
    case 'theme':
      this.shadowRoot.host.classList.toggle('dark', newVal === 'dark');
      break;
  }
}
4
disconnectedCallback() DOM から削除された時

要素が DOM から切断された時に呼ばれる。イベントリスナーの解除、タイマーのクリア、外部リソースの解放を行う。メモリリーク防止に重要。

disconnectedCallback() {
  this.removeEventListener('click', this._onClick);
  clearInterval(this._timer);
  this._observer?.disconnect();
}
属性変更を監視する例
class UserCard extends HTMLElement {
  static observedAttributes = ['name', 'avatar'];

  attributeChangedCallback(attr, oldVal, newVal) {
    if (attr === 'name') {
      this.shadowRoot.querySelector('.name').textContent = newVal;
    }
  }

  connectedCallback() {
    const shadow = this.attachShadow({ mode: 'open' });
    shadow.innerHTML = `
      <style>
        :host { display: block; padding: 1rem; border: 1px solid #ddd; border-radius: 8px; }
        .name { font-weight: bold; font-size: 1.2rem; }
      </style>
      <div class="name">${this.getAttribute('name') ?? ''}</div>
      <slot></slot>
    `;
  }
}
customElements.define('user-card', UserCard);
<user-card name="Alice">Frontend Developer</user-card>

Shadow DOM

コンポーネントの内部構造とスタイルを外部から隔離する仕組み

カプセル化された「シャドウ」DOM ツリーを要素に紐付け、関連する機能を制御するための JavaScript API です。シャドウ DOM ツリーはメイン文書の DOM とは別にレンダリングされるため、要素の内部構造やスタイルを外部から隔離できます。これにより、コンポーネントのスタイルがページの他の部分と衝突することを防げます。

DOM 構造の比較
通常の DOM
<div>
<style> ⚠ グローバル
<p>
Shadow DOM
<my-element>
#shadow-root
<style> ✓ 隔離
<p>
<slot>
mode の違い
mode: 'open'
element.shadowRoot

外部の JS から shadowRoot にアクセス可能。一般的な選択肢。

mode: 'closed'
element.shadowRoot → null

外部からアクセス不可。ただしセキュリティ境界ではない点に注意。

Shadow DOM の作成
class MyCard extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    shadow.innerHTML = `
      <style>
        /* このスタイルは外部に影響しない */
        :host { display: block; padding: 1rem; border: 1px solid #ddd; border-radius: 8px; }
        p { color: blue; font-weight: bold; }
      </style>
      <p><slot></slot></p>
    `;
  }
}
customElements.define('my-card', MyCard);
Shadow DOM の CSS セレクタ
:host シャドウホスト自身をスタイル
:host(.active) 条件付きでホストをスタイル
::slotted(span) スロットに挿入された要素をスタイル
::part(header) 外部から shadow 内の part をスタイル
CSS カスタムプロパティは Shadow DOM を貫通する

通常の CSS は Shadow DOM の内部に影響しませんが、CSS カスタムプロパティ(変数)は例外です。外部で定義した --my-color を Shadow DOM 内部で var(--my-color) として参照できます。これが Web Components のテーマ機能の基盤です。

:root {
  --card-bg: #f8fafc;    /* 外部で定義 */
  --card-text: #1e293b;
}

/* Shadow DOM 内で参照可能 */
:host {
  background: var(--card-bg);
  color: var(--card-text);
}

HTML Templates

レンダリングされないマークアップを定義し、JavaScript で再利用する仕組み

<template> と <slot> 要素によって、レンダリングされたページに表示されないマークアップのテンプレートを書くことができます。カスタム要素の構造の基礎として何度でも再利用でき、slot を使えば外部からコンテンツを差し込む柔軟な設計が可能です。

テンプレートの流れ
1
定義
<template>

HTML に書くが描画されない

2
複製
.cloneNode(true)

JS でテンプレートを複製

3
挿入
<slot>

Shadow DOM に追加して描画

スロットの種類
デフォルトスロット
<slot>

名前なし。slot 属性を持たない全ての子要素を受け取る。

名前付きスロット
<slot name="header">

対応する slot 属性を持つ要素のみを受け取る。

テンプレートとスロットの使用例
<!-- テンプレート定義 -->
<template id="card-template">
  <style>
    .card { border: 1px solid #ddd; padding: 1rem; border-radius: 8px; }
    .card-header { font-weight: bold; margin-bottom: 0.5rem; }
    .card-footer { margin-top: 0.5rem; font-size: 0.85rem; color: #666; }
  </style>
  <div class="card">
    <div class="card-header"><slot name="title">Default Title</slot></div>
    <div><slot>Default content</slot></div>
    <div class="card-footer"><slot name="footer"></slot></div>
  </div>
</template>
<!-- 使用: 複数のスロットにコンテンツを配置 -->
<my-card>
  <span slot="title">Web Components Guide</span>
  <p>Learn about Custom Elements, Shadow DOM, and Templates.</p>
  <span slot="footer">Last updated: 2026</span>
</my-card>
JavaScript でテンプレートを使用
class MyCard extends HTMLElement {
  connectedCallback() {
    const template = document.getElementById('card-template');
    const shadow = this.attachShadow({ mode: 'open' });
    shadow.appendChild(template.content.cloneNode(true));
  }
}
customElements.define('my-card', MyCard);

まとめ: 3 つを組み合わせた実践例

Custom Elements・Shadow DOM・HTML Templates の 3 つを組み合わせると、フレームワークに依存しない再利用可能なコンポーネントが完成します。以下は、プロフィールカードを Web Components で実装した例です。

Custom Elements
クラス定義 + ライフサイクル
+
Shadow DOM
スタイル隔離 + カプセル化
+
HTML Templates
テンプレート再利用 + スロット
=
完成
<profile-card>

Web Components デモ: <profile-card>

コードを編集して「実行」を押すと、右側のプレビューにリアルタイムで反映されます。スロットの内容やテーマ属性を変更してみてください。

プレビュー全画面表示
💡
ポイント

この <profile-card> は React・Vue・Svelte などどのフレームワークからでも使用できます。フレームワークに依存しないため、デザインシステムの共通コンポーネントとして最適です。

対応ブラウザ

機能 デスクトップ モバイル
Chrome
Edge
Firefox
Safari
Chrome Android
Safari iOS
26
13
22
8
26
8
DOM API

CustomElementRegistry インターフェイスは、カスタム要素の登録と、登録された要素を照会するためのメソッドを提供します。このインスタンスを取得するには、window.customElements プロパティを使用してください。

54
79
63
10.1
54
10.3

define() は CustomElementRegistry インターフェイスのメソッドで、新しいカスタム要素を定義します。

54
79
63
10.1
54
10.3
define (disabledFeatures static property)

「disabledFeature」静的プロパティをサポートします

77
79
92
77

get() は CustomElementRegistry インターフェイスのメソッドで、以前定義したカスタム要素のコンストラクターを返します。

54
79
63
10.1
54
10.3

CustomElementRegistry インターフェイスの getName() メソッドは、以前に定義されたカスタム要素の名前を返します。

117
117
116
17
117
17

upgrade() は CustomElementRegistry インターフェイスのメソッドで、 Node サブツリー内のすべてのシャドウを含むカスタム要素を、メイン文書に接続する前であってもアップグレードします。

68
79
63
12.1
68
12.2

whenDefined() は CustomElementRegistry インターフェイスのメソッドで、指定した名前の要素が定義されたときに解決されるプロミス (Promise) を返します。

54
79
63
10.1
54
10.3

Element.attachShadow() メソッドは、シャドウ DOM ツリーを特定の要素に追加し、そのシャドウルート (ShadowRoot) への参照を返します。

53
79
63
10
53
10

A boolean that specifies whether the shadow root is clonable: when set to true, the shadow host cloned with Node.cloneNode() or Document.importNode() will include shadow root in the copy. Its default value is false.

124
124
123
17.4
124
17.4

論理値で、 true に設定された場合、フォーカス可能性に関するカスタム要素の問題を緩和します。シャドウ DOM のフォーカスができない部分がクリックされた場合、最初のフォーカス可能な部分がフォーカスを得て、シャドウホストは :focus のスタイルを利用することができます。既定値は false です。

53
79
94
13.1
53
13.4

A boolean that, when set to true, indicates that the shadow root is serializable. If set, the shadow root may be serialized by calling the Element.getHTML() or ShadowRoot.getHTML() methods with the options.serializableShadowRoots parameter set true. Its default value is false.

125
125
128
18
125
18

Element.shadowRoot は読み取り専用のプロパティで、その要素がホストになっているシャドウルートを表します。

35
79
63
10
35
10

composed は Event インターフェイスの読み取り専用プロパティで、イベントがシャドウ DOM 境界を越えて標準 DOM に伝播するかどうかを示す論理値を返すものです。

53
79
52
10
53
10

composedPath() は Event インターフェイスのメソッドで、イベントの経路をリスナーが呼び出されるオブジェクトの配列で返します。シャドウルートが ShadowRoot.mode が closed の状態で作成された場合、シャドウツリーのノードは含まれません。

53
79
59
10
53
10

HTMLTemplateElement インターフェイスは、HTML の template 要素の内容にアクセスできるようにします。

26
13
22
8
26
8

HTMLTemplateElement.content プロパティは、 要素のテンプレートの内容 (DocumentFragment) を返します。

26
13
22
8
26
8

getRootNode() は Node インターフェイスのメソッドで、そのコンテキストのオブジェクトのルート、利用できる場合はオプションでシャドウルートを含んだものを返します。

54
79
53
10.1
54
10.3

isConnected は Node インターフェイスの読み出し専用のプロパティで、ノードが Document オブジェクトに(直接的または間接的に)接続されているかどうかを示す論理値を返します。

51
79
49
10
51
10

ShadowRoot はシャドウ DOM API のインターフェイスで、文書の DOM ツリーから分離してレンダリングされた部分ツリーのルートノードを指します。

53
79
63
10
53
10

clonable は ShadowRoot インターフェイスの読み取り専用プロパティで、シャドウルートが複製可能であれば true を返し、そうでなければ false を返します。

124
124
123
17.4
124
17.4

delegatesFocus は ShadowRoot インターフェイスの読み取り専用プロパティで、シャドウルートがフォーカスを委任する場合は true、そうでなければ false を返します。

53
79
94
15
53
15

host は ShadowRoot の読み取り専用プロパティで、 ShadowRoot が装着されている DOM 要素の参照を返します。

53
79
63
10
53
10

mode は ShadowRoot の読み取り専用プロパティで、モードを open と closed のどちらかで示します。 これはシャドウルートの内部機能を JavaScript からアクセスできるかどうかを定義します。

53
79
63
10.1
53
10.3

serializable は ShadowRoot インターフェイスの読み取り専用プロパティで、このシャドウルートがシリアライズ可能であれば、true を返します。

125
125
128
18
125
18

customElements は Window インターフェイスの読み取り専用プロパティで、 CustomElementRegistry オブジェクトへのリファレンスを返します。これにより、新しいカスタム要素を登録したり、以前に登録したカスタム要素に関する情報を取得したりすることができます。

54
79
63
10.1
54
10.3
その他

`:defined`

54
79
63
10
54
10
1+対応 (バージョン) 未対応 注釈あり サブ機能の解説は MDN Web Docs (CC BY-SA 2.5)
注釈 2件
制限事項
  • このブラウザでは部分的にしか実装されていません
実装メモ
  • 「自律カスタム要素」はサポートされますが、「カスタマイズされた組み込み要素」はサポートされません。 バグ 182671 を参照してください。
注釈 2件
制限事項
  • このブラウザでは部分的にしか実装されていません
実装メモ
  • 「自律カスタム要素」はサポートされますが、「カスタマイズされた組み込み要素」はサポートされません。 バグ 182671 を参照してください。
注釈 2件
制限事項
  • このブラウザでは部分的にしか実装されていません
実装メモ
  • 「自律カスタム要素」はサポートされますが、「カスタマイズされた組み込み要素」はサポートされません。
注釈 2件
制限事項
  • このブラウザでは部分的にしか実装されていません
実装メモ
  • 「自律カスタム要素」はサポートされますが、「カスタマイズされた組み込み要素」はサポートされません。
注釈 1件
対応条件
  • 以前は別名で対応していました: cloneable (16.4)
注釈 1件
対応条件
  • 以前は別名で対応していました: cloneable (16.4)
注釈 1件
実装メモ
  • Firefox 95 以前では、このプロパティは `<select>` 要素と `<input type='checkbox'>` 要素で誤って `false` に設定されていました。
注釈 2件
削除済み
  • このバージョンで機能が削除されました (53)
対応条件
  • 以前は別名で対応していました: deepPath (50)
注釈 2件
削除済み
  • このバージョンで機能が削除されました (53)
対応条件
  • 以前は別名で対応していました: deepPath (50)

アクセシビリティの注意点

Shadow DOM の内部コンテンツはスクリーンリーダーからアクセス可能ですが、カスタム要素名だけでは意味が伝わりません。内部にセマンティックな HTML 要素を配置し、適切な ARIA 属性を設定する必要があります。

✗ 不適切
<!-- カスタム要素名だけでは意味が伝わらない -->
<my-button>送信</my-button>

スクリーンリーダーは <my-button> をどう扱うか分からない。ボタンとして認識されず、キーボード操作もできない。

✓ 適切
<!-- 内部にセマンティック要素を配置 -->
<my-button>
  #shadow-root
    <button role="button" aria-label="送信">
      <slot></slot>
    </button>
</my-button>

内部に <button> を配置することで、スクリーンリーダーがボタンとして認識し、フォーカス・キーボード操作も自動的に有効になる。

チェックリスト
  • インタラクティブ要素には <button>、<a>、<input> 等のネイティブ要素を使う
  • カスタム要素に適切な role 属性を設定する(role="tablist"、role="dialog" 等)
  • aria-label / aria-describedby でコンテキスト情報を提供する
  • キーボード操作(Tab / Enter / Space / Escape)を実装する
  • フォーカス管理: Shadow DOM 内のフォーカス可能要素に delegatesFocus を検討する

セキュリティの観点

Shadow DOM はセキュリティ境界ではない

Shadow DOM はスタイルのカプセル化を提供するが、JavaScript からは element.shadowRoot でアクセス可能。機密データの隠蔽には使えない。

innerHTML への直接挿入の危険性

Custom Elements 内で innerHTML にユーザー入力を直接設定すると XSS 脆弱性が生じる。textContent や DOM API を使って安全にコンテンツを挿入すること。