Shadow 邊界會改變語意和 ARIA 的運作方式。主要有兩種失敗情況:Shadow Tree 內缺少語意,以及 ARIA 參照因無法跨越 Shadow Root 而自動中斷。
從語意標記開始:在範本中使用實際的 <button>、<nav>、<ul>,這樣大部分語意都會自動產生,並保留 ElementInternals/ARIA 用於填補空白。
透過 ElementInternals ARIA mixin 設定預設語意
透過 ElementInternals 在 JS 中設定自訂元素的角色和 ARIA 狀態,而不是在主機上寫入 ARIA 屬性。這樣一來,元件就能保留預設語意,同時讓使用者透過 Light 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內的 Tab 鍵順序中 (if (!this.hasAttribute('tabindex')) this.tabIndex = 0- 絕不要放在建構函式中,否則會在document.createElement上擲回),這樣焦點就會落在主機上,而輔助技術會宣告其角色和狀態。請勿在靜態、非互動式元件 (例如摘要或資訊卡) 的主機上設定tabindex。 - 當元件包裝擁有語意的內部可聚焦控制項時,請使用
delegatesFocus: true附加 Shadow Root (或在具有static formAssociated = true的表單關聯自訂元素上)。聚焦主體 (並點選表單關聯元素的關聯<label>) 接著會將焦點轉送至第一個可聚焦的子項,且:focus會與主體相符。請勿將delegatesFocus: true與主機上的角色/狀態合併,否則焦點會落在內部元素上,並略過主機的狀態。 - 如要移動焦點或將焦點移至新顯示的區域,請為目標提供
tabindex="-1"並呼叫.focus();請明確指定可透過程式輔助方式聚焦的目標,而非依賴分頁順序。
跨 Shadow Root 的 ARIA 關係
- 強制:
aria-labelledby、aria-describedby、aria-controls等參照的id只能在參照元素「本身」的樹狀結構中解析。Light DOM 中以id為基礎的屬性無法指向 Shadow Tree 內的id,反之亦然;系統會忽略參照,且不會顯示任何訊息。 - 如要跨越邊界,請使用反映的 ARIA 元素屬性 (例如
ariaLabelledByElements、ariaDescribedByElements等),這些屬性會採用直接 JS 元素參照,而非 ID。它們會向外解析:shadow tree 內的元素可以指向 Light DOM 或任何祖系樹狀結構 (包括插槽內容) 中的目標,因此您可以從 Light DOM 內容標示 shadow 內部控制項,這是id參照無法表達的。 - 但不會向內解析:元素無法參照後代 shadow tree (例如主體無法由自己的 shadow 內容標示)。新興的參考目標機制是標準方向,但尚未廣泛推出。
- 如果無法以任何一種方式表示關係,請將關係保留在一個樹狀結構中 (在同一個樹狀結構中顯示標籤),或使用
aria-label文字,而非id參照。
備用策略
如果無法使用 ElementInternals ARIA mixin,請改為主機上的屬性設定角色和 ARIA 狀態 (this.setAttribute('role', 'switch'))。這種做法適用於所有情況,但代價是將屬性放在 DOM 中,而使用者可能會覆寫這些屬性。
如果無法使用反映的 ARIA 元素屬性,請使用以 id 為基礎的 aria-* 屬性來建立同樹狀結構關係;跨邊界標籤沒有以 id 為基礎的對應屬性,因此請改用 aria-label 文字。