섀도우 경계는 시맨틱스와 ARIA의 작동 방식을 변경합니다. 두 가지 실패가 지배적입니다. 섀도 트리의 시맨틱 누락과 섀도 루트를 교차할 수 없기 때문에 자동으로 중단되는 ARIA 참조입니다.
시맨틱 마크업에서 시작: 템플릿 내에서 실제 <button>, <nav>, <ul>를 사용하여 대부분의 시맨틱을 무료로 가져오고 ElementInternals/ARIA를 공백으로 예약합니다.
ElementInternals ARIA mixin을 통한 기본 시맨틱스
호스트에 ARIA 속성을 작성하는 대신 ElementInternals를 통해 JS에서 맞춤 요소의 역할과 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내의 탭 순서에 호스트를 배치합니다(if (!this.hasAttribute('tabindex')) this.tabIndex = 0— 생성자에는 절대 배치하지 않음,document.createElement에서 발생). 이렇게 하면 호스트에 포커스가 맞춰지고 보조 기술이 역할과 상태를 알립니다. 정적이고 상호작용이 불가능한 구성요소 (예: 콜아웃 또는 카드)의 호스트에는tabindex를 설정하지 마세요. - 구성요소가 시맨틱을 소유하는 내부 포커스 가능 컨트롤을 래핑하는 경우 (또는
static formAssociated = true가 있는 양식 연결 맞춤 요소에서)delegatesFocus: true를 사용하여 shadow root를 연결합니다. 그러면 호스트에 포커스를 맞추고 (양식 연결 요소의 경우 연결된<label>를 클릭) 포커스를 포커스 가능한 첫 번째 하위 요소로 전달하고:focus가 호스트와 일치합니다.delegatesFocus: true를 호스트의 역할/상태와 결합하지 마세요. 결합하면 포커스가 내부 요소에 적용되고 호스트의 상태는 건너뜁니다. - 포커스를 이동하거나 새로 표시된 영역으로 포커스를 이동하려면 타겟에
tabindex="-1"를 지정하고.focus()를 호출합니다. 탭 순서에 의존하는 대신 프로그래매틱 방식으로 포커스를 맞출 수 있는 타겟을 명시적으로 지정합니다.
섀도 루트 간 ARIA 관계
- 필수:
aria-labelledby,aria-describedby,aria-controls등으로 참조되는id는 참조하는 요소의 자체 트리 내에서만 확인됩니다. Light DOM의id기반 속성은 shadow 트리 내의id를 가리킬 수 없으며 그 반대의 경우도 마찬가지입니다. 참조는 자동으로 무시됩니다. - 경계를 넘으려면 ID 대신 직접 JS 요소 참조를 사용하는 반영된 ARIA 요소 속성 (예:
ariaLabelledByElements,ariaDescribedByElements등)을 사용하세요. 외부로 확인됩니다. shadow 트리 내의 요소는 Light DOM 또는 상위 트리 (슬롯 콘텐츠 포함)의 타겟을 가리킬 수 있으므로 Light DOM 콘텐츠에서 shadow 내부 컨트롤에 라벨을 지정할 수 있습니다. 이는id참조로 표현할 수 없는 기능입니다. - 안쪽으로는 확인되지 않습니다. 즉, 요소가 하위 shadow 트리로 참조할 수 없습니다 (예: 호스트는 자체 shadow 콘텐츠로 라벨을 지정할 수 없음). 이러한 방향의 경우 새로운 참조 타겟 메커니즘이 표준 방향이지만 아직 널리 제공되지는 않습니다.
- 어느 쪽으로도 관계를 표현할 수 없는 경우 하나의 트리 내에 유지하거나 (동일한 트리에서 라벨 렌더링)
id참조 대신aria-label텍스트를 사용합니다.
대체 전략
ElementInternals ARIA mixin을 사용할 수 없는 경우 대신 호스트에서 역할 및 ARIA 상태를 속성으로 설정합니다 (this.setAttribute('role', 'switch')). 이 방법은 어디서나 작동하지만 소비자가 재정의할 수 있는 속성을 DOM에 배치해야 합니다.
반영된 ARIA 요소 속성을 사용할 수 없는 경우 동일 트리 관계에 id 기반 aria-* 속성을 사용합니다. 경계 간 라벨에는 id 기반 상응 속성이 없으므로 aria-label 텍스트로 대체합니다.