Web Components は、カスタム要素、Shadow DOM、HTML テンプレートです。これらは、マークアップとスタイルがドキュメントからカプセル化された、再利用可能でフレームワークに依存しないコンポーネントのプラットフォーム プリミティブです。デザイン システム、埋め込み可能なウィジェット、自己完結型の UI に使用します。
以下のルールは、複数のカテゴリにまたがるものです。手元のタスクに固有のガイドを取得します。各ガイドには、必要なコンテキストが再記述され、そのタスクに固有の API と注意点が記載されています。
カスタム要素が必要かどうかを判断する
カスタム要素を使用するのは、カプセル化された動作、ライフサイクル、またはタグで再利用可能な JS ベースの API が必要な場合のみです。次のような場合は、作成しないでください。
- CSS を使用したプレーンな HTML 要素で十分です。スタイル設定された
<button>または<details>は、再実装よりもアクセスしやすく、コストもかかりません。 - このオブジェクトは純粋にプレゼンテーション用であり、動作はありません。ネイティブ要素のクラスはパフォーマンスが高く、
@layerまたは@scopeでスコープを設定できます。 - インタラクティブ機能のないサーバーでのみスタイル スコープが必要な場合は、スコープ付きスタイルシートまたは宣言型 Shadow DOM(アップグレードなし)で十分な場合があります。
ネイティブ セマンティクスを再構築する(フォーカス、アクティベーション、ARIA を再作成するカスタム <my-button>)ことは、最も一般的な誤用です。ネイティブ要素を再利用する場合にのみ、プラットフォームのユーザー補助機能を無料で継承できます。<my-button> スタイルのラッパーが正当化される場合(通常は、共有スタイリング フック、追加のマークアップ、ネイティブ要素では表現できない動作の場合)、実際の <button> を再実装するのではなく、ラップします(内部のネイティブ要素にスタイルを転送する方法については、ウェブ コンポーネントのスタイル設定をご覧ください)。
テストは、カプセル化された状態、スクリプト可能な API、またはネイティブ要素では表現できない動作が必要かどうかを判断するものであり、単に外観が異なるかどうかを判断するものではありません。高評価 / お気に入りの切り替えは、ラッチされたドメイン状態を保持し、その状態を周囲のページのスタイル設定フックとして公開し、独自の change イベントをディスパッチします。スタイル設定された <button> では、これらの機能は提供されません。タップされたときに色が変わるだけのボタンは、<button> + CSS には対応していません。作成する場合は、必要に応じてカスタム状態または属性を使用して状態をモデル化します(カスタム要素を参照)。
ツールをコードベースに合わせる
カスタム要素にすべきかどうかは、プロジェクトですでに使用されているものによって異なります。エージェントは過度に抽象化し、新しい要素を熱心に求めがちです。迷ったときは、通常、より軽いオプション(ネイティブ要素、CSS、サーバー テンプレート)が適切です。再利用や動作が実際に必要とする場合は、後でカスタム要素に昇格できます。ヒューリスティック(優先度順):
- コードベースにすでにコンポーネント モデル(React、Vue、Svelte など)がある。アプリ内 UI には、そのフレームワークのコンポーネントをデフォルトで使用します。カスタム要素を使用するのは、フレームワークから逃れる必要がある場合や、フレームワークよりも長く存続する必要がある場合のみです。たとえば、アプリやフレームワーク間で共有されるデザイン システム プリミティブ、フレームワークが制御しないマークアップにドロップされたウィジェット、プレーン HTML として機能する必要がある出力(メール、CMS、SSR フラグメント)などです。通常のアプリ内ビューに並列コンポーネント システムを立ち上げないでください。
- ツールは不要で、ほとんどが静的なマークアップです。プレーンな HTML + CSS、またはサーバー テンプレート / パーシャルを使用することをおすすめします。カスタム要素は、タグに動作、ライフサイクル、JS ベースの API が本当に必要な場合にのみ、その価値を発揮します。単にマークアップを共有するためだけに使用するものではありません。マークアップの共有は、サーバー インクルードや
<template>で JS を送信せずに行うことができます。 - インタラクティブなアイランドを備えた静的サイト ジェネレータ。マークアップを静的にレンダリングし、インタラクティブな部分にカスタム要素を予約します。「マークアップが多く、インタラクティビティが少ない」場合は、JS からすべてを再レンダリングするのではなく、マークアップを Light DOM(または宣言型 Shadow DOM)に保持し、小さなカスタム要素でプログレッシブ エンハンスメントを行います。
- 代わりに、組み込み要素をさらにプッシュすることもできます。通常は、カスタム要素よりもネイティブ要素を可能な限り使用する(CSS、いくつかのリスナー、場合によっては疑似要素)方が、プラットフォームのユーザー補助機能と動作をそのまま維持できるため、望ましいです。回避策が維持できなくなった場合、つまり、要素の組み込み動作と競合する場合や、オーサリング エクスペリエンスがクリーンなコンポーネント API よりも悪化する場合は、カスタム要素に移行します。組み込み要素に少しの JS が必要だからといって、それだけで置き換える理由にはなりません。
横断的なルール
以下のすべてのガイドに共通するルールは次のとおりです。
- 必須: タグ名は、少なくとも 1 つのハイフンを含むケバブケース(例:
my-element)にする必要があります。これにより、カスタム要素を現在および将来の組み込み要素と区別します。 - 必須:
thisにアクセスする前に、コンストラクタで最初にsuper()を呼び出します。thisの読み取りまたは書き込みを行うステートメントの後に呼び出すと、スローされます。 - 必須: シャドウ テンプレート内でセマンティック要素(
<button>、<nav>、<ul>)を使用します。シャドウツリーのユーザー補助機能の不具合のほとんどは、非セマンティック マークアップに起因しています。スロットは、投影するもののロールを変更しません。 - 必須: ARIA
id参照(aria-labelledby、aria-describedby、aria-controls)はシャドウ境界を越えることはできません。参照元の要素と参照先のidは同じツリーに存在する必要があります。 - デベロッパー ツール、ページ スクリプト、サーバー ハイドレーションが
element.shadowRootにアクセスできるように、attachShadow({ mode: 'open' })を優先します。'closed'は、ページ スクリプトから内部構造を隠す必要がある場合にのみ使用します。 - カスタマイズされた組み込み要素(
is="…"を含むextends HTMLButtonElement)は避けてください。Safari は、これらの要素の提供を完全に拒否しています。
ユースケース ガイド マトリックス
タスクを特定し、そのガイドを取得します。
| 必要な操作 | 取得 |
|---|---|
| 動作とライフサイクルを含む新しいセマンティック タグを定義する | カスタム要素 |
| スタイルとマークアップをコンポーネントにスコープし、コンシューマー コンテンツをコンポーネントに投影する | Shadow DOM、テンプレート、スロット |
| スタイル設定されていないコンテンツがフラッシュすることなく、サーバー(SSR)でコンポーネントのシャドウツリーをレンダリングする | カスタム要素の事前レンダリング |
シャドウ境界を越えたスタイル設定(テーマ設定、::part、カスタム プロパティ) |
ウェブ コンポーネントのスタイル設定 |
カスタム コントロールの値を、囲んでいる <form> に送信する |
フォームに関連付けられたカスタム要素 |
| カスタム要素をアクセス可能にする(ロール、フォーカス、シャドウルート間の ARIA) | アクセス可能なウェブ コンポーネント |