アクセス可能なウェブ コンポーネント

ウェブ機能: ARIA 属性の反映 Shadow DOM

シャドウ境界は、セマンティクスと ARIA の動作を変更します。主な失敗は 2 つあります。1 つはシャドウツリー内のセマンティクスが欠落していること、もう 1 つは ARIA 参照がシャドウルートをまたぐことができないため、サイレントに破損することです。

セマンティック マークアップから始める: テンプレート内で実際の <button>、<nav>、<ul> を使用して、ほとんどのセマンティクスを無料で取得し、ギャップに ElementInternals/ARIA を予約します。

ElementInternals ARIA mixin によるデフォルトのセマンティクス

ホストに ARIA 属性を書き込むのではなく、ElementInternals を介して JS でカスタム要素のロールと ARIA 状態を設定します。これにより、セマンティクスはコンポーネントが所有するデフォルトとして維持され、コンシューマはライト DOM の属性でオーバーライドできます。

class ToggleSwitch extends HTMLElement {
  #internals;
  constructor() {
    super();
    this.#internals = this.attachInternals();
    this.#internals.role = 'switch';          // default role, no host attribute needed
    this.#internals.ariaChecked = 'false';    // exposed to the accessibility tree
    this.addEventListener('click', () => this.toggle());
    this.addEventListener('keydown', (e) => {
      if (e.key === ' ') { e.preventDefault(); this.toggle(); }
    });
  }
  connectedCallback() {
    // Put the host in the tab order on connect (setting tabIndex in the constructor throws on createElement).
    if (!this.hasAttribute('tabindex')) this.tabIndex = 0;
  }
  toggle() {
    const on = this.#internals.ariaChecked !== 'true';
    this.#internals.ariaChecked = String(on);
  }
}
customElements.define('toggle-switch', ToggleSwitch);

これにより、ARIA 属性で DOM が汚染されるのを防ぎ、コンシューマが誤って属性を上書きするのを防ぐことができます。一方、ユーザー補助ツリーには正しいロールと状態が表示されます。

フォーカス管理と delegatesFocus

  • ホスト要素自体がインタラクティブ コントロールであり、ElementInternals を介してロールと状態を保持している場合(上記の ToggleSwitch など)、connectedCallback(if (!this.hasAttribute('tabindex')) this.tabIndex = 0。document.createElement でスローされるコンストラクタでは決してない)内のタブ順序にホストを配置します。これにより、ホストにフォーカスが移動し、支援技術がそのロールと状態をアナウンスします。静的でインタラクティブでないコンポーネント(コールアウトやカードなど)のホストに tabindex を設定しないでください。
  • コンポーネントがセマンティクスを所有する内部のフォーカス可能なコントロールをラップしている場合(または static formAssociated = true を含むフォーム関連のカスタム要素の場合)、delegatesFocus: true を使用してシャドウルートをアタッチします。ホストをフォーカスすると(フォーム関連の要素の場合は関連する <label> をクリックすると)、フォーカスが最初のフォーカス可能な子に転送され、:focus がホストと一致します。delegatesFocus: true をホストのロール / 状態と組み合わせないでください。フォーカスが内部要素に移動し、ホストの状態がスキップされます。
  • フォーカスを移動する場合や、新しく表示された領域にフォーカスを移動する場合は、ターゲットに tabindex="-1" を指定して .focus() を呼び出します。タブ順序に依存するのではなく、プログラムでフォーカス可能なターゲットを明示的に指定します。

シャドールート間の ARIA 関係

  • 必須: aria-labelledby、aria-describedby、aria-controls などによって参照される id は、参照元の要素の独自のツリー内でのみ解決されます。Light DOM の id ベースの属性は、シャドウツリー内の id を参照できません。その逆も同様です。参照は暗黙的に無視されます。
  • 境界を越えるには、反映された ARIA 要素プロパティ(ariaLabelledByElements、ariaDescribedByElements など)を使用します。これらのプロパティは、ID ではなく直接 JS 要素参照を受け取ります。これらは外側に解決されます。シャドウツリー内の要素は、Light DOM または任意の祖先ツリー(スロット化されたコンテンツを含む)内のターゲットを指すことができるため、Light DOM コンテンツからシャドウ内部コントロールにラベルを付けることができます。これは id 参照では表現できません。
  • 内向きには解決されません。要素は、下位のシャドウツリー内を参照できません(たとえば、ホストは独自のシャドウ コンテンツでラベル付けできません)。Reference Target メカニズムは標準的な方向性ですが、まだ広く利用可能ではありません。
  • どちらの方法でも関係を表現できない場合は、1 つのツリー内に保持するか(同じツリーにラベルをレンダリングする)、id 参照の代わりに aria-label テキストを使用します。

代替戦略

ElementInternals ARIA ミックスインが利用できない場合は、代わりにロールと ARIA 状態をホストの属性として設定します(this.setAttribute('role', 'switch'))。これはどこでも機能しますが、コンシューマがオーバーライドできる属性を DOM に配置することになります。

反映された ARIA 要素プロパティが使用できない場合は、同じツリーの関係に id ベースの aria-* 属性を使用します。境界を越えるラベルには id ベースの同等の属性がないため、aria-label テキストにフォールバックします。