シャドウ境界は、セマンティクスと 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 テキストにフォールバックします。