גבול צל משנה את אופן הפעולה של הסמנטיקה ושל ARIA. יש שני כשלים עיקריים: סמנטיקה חסרה בתוך עץ הצל, והפניות ARIA שנשברות באופן שקט כי הן לא יכולות לחצות שורשי צל.
מתחילים מתגי עיצוב סמנטיים: משתמשים בתגי <button>, <nav> ו-<ul> אמיתיים בתוך התבנית כדי שרוב הסמנטיקה תהיה זמינה בחינם, ושומרים את ElementInternals/ARIA לפערים.
סמנטיקה שמוגדרת כברירת מחדל באמצעות ElementInternals ARIA mixin
מגדירים את התפקיד ואת מצב ה-ARIA של רכיב מותאם אישית ב-JS באמצעות ElementInternals, במקום לכתוב מאפייני 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— אף פעם לא ב-constructor, שמושלך עלdocument.createElement) כך שהמיקוד ינחת על המארח והטכנולוגיה המסייעת תכריז על התפקיד והמצב שלו. אל תגדירו אתtabindexבמארח לרכיבים סטטיים ולא אינטראקטיביים (כמו הסבר קצר או כרטיס). - מצרפים את שורש הצללית עם
delegatesFocus: trueכשהרכיב עוטף פקד פנימי שניתן להתמקד בו והוא הבעלים של הסמנטיקה (או ברכיב מותאם אישית שמשויך לטופס עםstatic formAssociated = true). לאחר מכן, התמקדות במארח (ולחיצה על<label>משויך לרכיבים שמשויכים לטופס) מעבירה את המיקוד לצאצא הראשון שניתן להתמקד בו, ו-:focusתואם למארח. אל תשלבו אתdelegatesFocus: trueעם תפקיד או מצב במארח, אחרת המיקוד יתמקד באלמנט הפנימי וידלג על המצב של המארח. - כדי להעביר את המיקוד או להעביר את המיקוד לאזור חדש שמוצג, צריך לתת ליעד
tabindex="-1"ולקרוא ל-.focus(). צריך להגדיר יעדים שאפשר להתמקד בהם באופן פרוגרמטי במפורש, במקום להסתמך על סדר הכרטיסיות.
קשרי ARIA בין שורשי צל (shadow roots)
- חובה:
idשמופנה על ידיaria-labelledby,aria-describedby,aria-controlsוכו' נפתר רק בתוך העץ של הרכיב המפנה. מאפיין שמבוסס עלidב-DOM הקל לא יכול להצביע עלidבתוך עץ צל, ולהפך. המערכת מתעלמת מההפניה בלי להציג הודעה. - כדי לחצות את הגבול, משתמשים במאפייני הרכיב של ARIA שמשתקפים (למשל
ariaLabelledByElements,ariaDescribedByElementsוכו'), שמקבלים הפניות ישירות לרכיבי JS במקום מזהים. הם פותרים כלפי חוץ: רכיב בתוך עץ צל יכול להצביע על יעד ב-Light DOM או על כל עץ אב (כולל תוכן מחורץ), כך שתוכלו לתייג פקד פנימי של צל מתוכן Light DOM – משהו שהפניותidלא יכולות לבטא. - הם לא פותרים פנימה: רכיב לא יכול להפנות אל עץ צל צאצא (למשל, מארח לא יכול להיות מתויג על ידי תוכן הצל שלו). בכיוון הזה, מנגנון Reference Target המתפתח הוא הכיוון של התקנים, אבל הוא עדיין לא זמין באופן נרחב.
- אם אי אפשר לבטא קשר באחת מהדרכים האלה, צריך להשאיר אותו בתוך עץ אחד (להציג את התווית באותו עץ) או להשתמש בטקסט
aria-labelבמקום בהפניה ל-id.
אסטרטגיות חלופיות
אם ה-mixin של ARIA ElementInternals לא זמין, צריך להגדיר את התפקיד ואת מצב ה-ARIA כמאפיינים במארח (this.setAttribute('role', 'switch')). זה עובד בכל מקום, אבל צריך להוסיף מאפיינים ל-DOM שצרכן יכול לשנות.
אם מאפייני הרכיב של ARIA שמשתקפים לא זמינים, צריך להשתמש במאפיין aria-* שמבוסס על id ליחסים באותו עץ. לתווית שחוצה גבולות אין מקבילה שמבוססת על id, ולכן צריך לחזור לטקסט aria-label.