Start · Sprachen · JavaScript · Referenz · ElementInternals.ariaControlsElements

ElementInternals.ariaControlsElements

Eigenschaft

Legt als Standard-Semantik eines Custom Elements fest, welche Elemente es steuert. Entspricht <code>aria-controls</code>, arbeitet aber mit Elementreferenzen statt mit IDs.

seit JavaScript Chrome/Edge 135+, Firefox 136+, Safari 16.4+ (noch Kategorie: webapi

Signatur

ElementInternals.ariaControlsElements

Beschreibung

Mit ariaControlsElements auf einem ElementInternals-Objekt legt ein autonomes Custom Element fest, welche Elemente es steuert. Assistive Technologien wie Screenreader erfahren so zum Beispiel, dass ein eigener Aufklapp-Button ein bestimmtes Panel ein- und ausblendet oder dass ein eigenes Suchfeld eine Ergebnisliste aktualisiert.

Anders als das HTML-Attribut aria-controls braucht die Eigenschaft keine ID-Liste. Sie erwartet direkt ein Array von Element-Objekten. Das ist Element Reflection: Die Elemente brauchen keine id, und die Beziehung bleibt erhalten, auch wenn sich IDs ändern.

Über ElementInternals gesetzte Werte sind Standard-Semantik und im DOM unsichtbar. Es entsteht also kein Attribut am Host-Element. Setzt der Nutzer des Elements selbst aria-controls oder element.ariaControlsElements, hat dieser Wert Vorrang. So kann eine Komponente sinnvolle Vorgaben machen, ohne das Markup ihrer Nutzer zu überschreiben.

Das Objekt ElementInternals bekommt man nur einmal, über this.attachInternals() im Konstruktor des Custom Elements.

Parameter

Name Typ Default Beschreibung
$value (Setter) Array<Element> | null null Neue Liste der gesteuerten Elemente. null entfernt die Beziehung. Das Array muss immer als Ganzes neu zugewiesen werden.

Rückgabewert

Typ
Array<Element> | null
Beschreibung
Ein eingefrorenes Array mit den aktuell gültigen gesteuerten Elementen oder null, wenn nichts gesetzt ist. Elemente, die (noch) nicht erlaubt sind, etwa weil sie in einem nicht zugänglichen Shadow Tree liegen, fehlen im Ergebnis.

Beispiele

Eigener Aufklapp-Button steuert ein Panel

class DisclosureButton extends HTMLElement {
  #internals;

  constructor() {
    super();
    this.#internals = this.attachInternals();
    this.#internals.role = 'button';
    this.#internals.ariaExpanded = 'false';
    this.tabIndex = 0;
    this.addEventListener('click', () => this.toggle());
  }

  connectedCallback() {
    // Ziel-Panel über ein data-Attribut finden (liegt im Light DOM)
    const panel = document.querySelector(this.dataset.target);
    if (panel) {
      this.#internals.ariaControlsElements = [panel];
    }
  }

  toggle() {
    const [panel] = this.#internals.ariaControlsElements ?? [];
    if (!panel) return;
    panel.hidden = !panel.hidden;
    this.#internals.ariaExpanded = String(!panel.hidden);
  }
}

customElements.define('disclosure-button', DisclosureButton);

// HTML:
// <disclosure-button data-target="#details">Details</disclosure-button>
// <div id="details" hidden>Versteckter Inhalt</div>

Mehrere gesteuerte Elemente setzen und auslesen

class FilterBox extends HTMLElement {
  internals = this.attachInternals();

  connectedCallback() {
    this.internals.role = 'searchbox';
    const list = document.getElementById('results');
    const counter = document.getElementById('result-count');
    // Immer ein neues Array zuweisen
    this.internals.ariaControlsElements = [list, counter];
  }
}
customElements.define('filter-box', FilterBox);

const box = document.querySelector('filter-box');
console.log(box.internals.ariaControlsElements.map((el) => el.id));
["results", "result-count"]

Beziehung entfernen und Fallstrick beim Mutieren

const internals = document.querySelector('filter-box').internals;

// FALSCH: Das zurückgegebene Array ist eingefroren
try {
  internals.ariaControlsElements.push(document.body);
} catch (err) {
  console.log(err instanceof TypeError);
}

// RICHTIG: neu zuweisen
internals.ariaControlsElements = [
  ...(internals.ariaControlsElements ?? []),
  document.getElementById('extra')
];

// Beziehung komplett entfernen
internals.ariaControlsElements = null;
console.log(internals.ariaControlsElements);
true null

// Wichtig · Fallstricke

  • Browser-Unterstützung: Element Reflection für ARIA-Beziehungen ist relativ neu: Safari ab 16.4, Chrome/Edge ab 135, Firefox ab 136. Für ältere Browser braucht man einen Fallback, z. B. aria-controls mit IDs am Host setzen. Vorher prüfen mit 'ariaControlsElements' in ElementInternals.prototype.
  • Grenzen des Shadow DOM: Erlaubt sind nur Elemente im selben Baum wie das Element oder in einem seiner übergeordneten Bäume, also zum Beispiel im Light DOM des Dokuments. Elemente im eigenen oder einem fremden, tiefer liegenden Shadow Root werden beim Auslesen herausgefiltert und für die Barrierefreiheit ignoriert.
  • Vorrang: Ein aria-controls-Attribut oder element.ariaControlsElements am Host überschreibt den Standardwert aus ElementInternals.
  • Nicht mutieren: Der Getter liefert ein eingefrorenes Array. Änderungen gehen nur durch Neuzuweisen.
  • Die Referenzen sind schwach: Wird ein Element aus dem DOM entfernt, fällt es aus dem Ergebnis heraus. Speicherlecks entstehen dadurch nicht.
  • aria-controls wird von vielen Screenreadern nur eingeschränkt angesagt. Deshalb zusätzlich Zustände wie ariaExpanded pflegen.