Ein CSS-Ansatz, um den Fokus innerhalb eines Elements einzuschließen

Avatar of Kushagra Gour
Kushagra Gour am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Kürzlich habe ich diesen Artikel von Keith Grant gelesen, der das neu erschienene Element <dialog> vorstellte. . Begeistert von diesem neuen UI-Element habe ich mich sofort daran gemacht, damit zu experimentieren, um zu sehen, wie es effektiv als Modal verwendet werden kann – was die häufigste Anwendung dafür ist. Bei meinen Experimenten habe ich einen netten CSS-Trick entdeckt, um den Fokus innerhalb des Elements einzuschließen, eine gängige Anforderung an die Zugänglichkeit für Modals und eine bekanntermaßen schwierige.

Haftungsausschluss: Die <dialog>-Demos in diesem Artikel werden nur in Chrome und Firefox getestet. Safari hat ein merkwürdiges Problem, bei dem nicht alle Elemente bei der normalen Tastaturnavigation mit der Tab-Taste fokussiert werden!

Was ist Fokus-Falle?

Zuerst ein Zitat aus der W3C-Dokumentation darüber, was nach einem Tastendruck innerhalb eines Dialogs geschehen soll

Tab

  • Verschiebt den Fokus auf das nächste fokussierbare Element im Dialog.
  • Wenn sich der Fokus auf dem letzten fokussierbaren Element im Dialog befindet, wird der Fokus auf das erste fokussierbare Element im Dialog verschoben.

Umschalttaste + Tab

  • Verschiebt den Fokus auf das vorherige fokussierbare Element im Dialog.
  • Wenn sich der Fokus auf dem ersten fokussierbaren Element im Dialog befindet, wird der Fokus auf das letzte fokussierbare Element im Dialog verschoben.

Zusammenfassend lässt sich sagen, dass beim Drücken von Tab oder Umschalttaste+Tab innerhalb eines Dialogs der Fokus nur innerhalb des Dialogs gewechselt werden sollte – zwischen den fokussierbaren Elementen innerhalb des Dialogs.

Das ergibt Sinn, denn wenn ein Dialog geöffnet ist, interagiert ein Benutzer nur innerhalb dieses Dialogs, und wenn der Fokus nach außen entweichen könnte, würde dies im Wesentlichen Kontexte vermischen und möglicherweise einen Zustand schaffen, in dem der Benutzer nicht weiß, welches Element den Fokus hat.

Wenn wir also zurück zur Idee eines Modals gehen, wäre unsere Erwartung, dass das Tabbing innerhalb des Modals nur Elemente innerhalb des Modals fokussiert. Alles außerhalb des Kontexts des Modals wäre außer Reichweite, da sich die Tabulatortaste nur auf das konzentriert, was sich darin befindet. Das ist es, was wir unter Fokus-Falle verstehen.

Eine Implementierung mit JavaScript

Wenn wir eine Fokus-Falle innerhalb eines <dialog>-Elements implementieren wollten, wäre der üblichste Ansatz, Folgendes zu tun, wenn der Dialog geöffnet wird:

1. Alle fokussierbaren/tab-fähigen Elemente innerhalb des Dialogs abrufen.
2. Auf Tab- und Umschalttaste+Tab-Tastendrücke reagieren und manuell zum nächsten bzw. vorherigen Element springen.
3. Wenn die Tastendruck auf dem ersten fokussierbaren Element erfolgt, dann auf das letzte fokussierbare Element in der Kette springen und umgekehrt.

Auf diese Weise erzeugen wir eine Fokus-Schleife, wenn der Benutzer Tab oder Umschalttaste+Tab drückt. Sehen Sie sich diese W3C-Code-Snippet als Beispiel dafür an, wie dies mit JavaScript angegangen werden könnte. Sie werden sehen, dass es *ziemlich viel* JavaScript ist.

Einführung in :focus-within

Zurück zu meinem Experiment mit dem neuen <dialog>-Element. Wenn ich über Fokus-Falle nachdachte, fiel mir sofort eine CSS-Pseudoklasse ein (ebenfalls sehr neu in Browsern): :focus-within.

Wenn Sie davon noch nichts gehört haben, repräsentiert es ein Element, das den Fokus erhalten hat oder ein Element enthält, das den Fokus erhalten hat. Wenn Sie also beispielsweise ein <div> haben und darin ein input-Element, können Sie dieses <div> wie folgt gestalten, wenn das enthaltene input den Fokus hat:

div:focus-within {
  border: 2px solid red;
}

Der CSS-Trick für Fokus-Falle

Nutzen wir :focus-within und CSS-Übergänge, um eine einfache Fokus-Falle innerhalb eines <dialog>-Elements zu implementieren.

Zusammenfassend lässt sich sagen, dass der Trick so funktioniert. Wenn der Fokus nicht im Dialog liegt (und der Dialog geöffnet ist), lösen wir Folgendes aus:

  1. eine CSS-Übergang auslösen
  2. den Abschluss des Übergangs in JavaScript erkennen
  3. das erste Element im Dialog fokussieren

Aber zuerst richten wir alles ein. Hier ist der grundlegende Dialog und die Öffnungsfunktionalität

<button id="button">Open dialog</button>
<dialog id="modal">
  <form action="">
    <label>
      <input type="text" /> Username
    </label>
    <label>
      <input type="password" /> Password
    </label>
    <input type="submit" value="Submit" />
  </form>
</dialog>
button.onclick = () => {
  modal.showModal();
}

Da haben wir es. Das Klicken auf den Button sollte unseren Dialog öffnen. Nur dieser Code ist erforderlich, um ein grundlegendes funktionierendes Modal mit dem neuen <dialog> zu erstellen.

Hinweis: Wie im obigen Beispiel-Demo des Dialogs sehen Sie einige zusätzliche Polyfill-Codes, um <dialog> in Browsern, in denen es nicht unterstützt wird, zum Laufen zu bringen.

Wenn Sie den Dialog im obigen Beispiel geöffnet und mehrmals mit Tabulator navigiert haben, haben Sie vielleicht schon das Problem bemerkt: Der Fokus beginnt bei Elementen im Dialog, verlässt ihn aber, sobald das letzte Element im Dialog passiert wurde.

Das ist der Kern unseres Tricks. Wir müssen irgendwie den verlorenen Fokus, der mit :focus-within erkannt wird, an JavaScript senden, damit wir den Fokus zurück zum Dialog senden können. Hier kommen CSS-Übergänge ins Spiel. Ein CSS-Übergang ist etwas, das über CSS geschieht, aber auch Ereignisse in JavaScript ausgibt. In unserem Fall können wir einen Übergang für eine beliebige Eigenschaft mit einem vernachlässigbaren (da er in unserem Fall keine Rolle spielt) visuellen Unterschied auslösen und auf den Abschluss des Übergangs in JavaScript warten.

Beachten Sie, dass wir diesen Übergang auslösen müssen, wenn der Dialog geöffnet ist, aber keinen Fokus darin hat.

dialog {
  background-color: rgb(255, 255, 255);
}
dialog[open]:not(:focus-within) {
  background-color: rgb(255, 255, 254);
  transition: background-color 0.01s;
}

Sehen wir uns an, was dieses CSS tut.

  1. Wir weisen dem Dialog eine background-color unserer Wahl zu. Dies ist nicht notwendig, stellt aber sicher, dass wir in allen Browsern die gleiche Hintergrundfarbe haben.
  2. Der Selektor dialog[open]:not(:focus-within) wird angewendet, wenn der Dialog geöffnet ist, aber keinen Fokus darauf oder darin hat. Das funktioniert, weil das native Element ein open-Attribut setzt, wenn es geöffnet ist.
  3. Innerhalb dieser Regel ändern wir die background-color um einen minimalen Betrag. Dies ist die geringste Änderung, die erforderlich ist, um eine CSS-Animation auszulösen und gleichzeitig keine visuellen Unterschiede für den Benutzer zu verursachen (denken Sie daran, dass dies ein Dummy-Übergang ist). Außerdem setzen wir die transition-Eigenschaft mit einer sehr kurzen Dauer, da wir möchten, dass sie so schnell wie möglich abgeschlossen wird und in JavaScript erkannt wird.

Ein Hauch von JavaScript

Jetzt müssen wir nur noch das Ende unseres ausgelösten CSS-Übergangs erkennen und das erste Element im Modal wieder fokussieren, wie folgt:

modal.addEventListener('transitionend', (e) => {
  modal.querySelector('input').focus();
});

Wir fügen dem Modal einen transitionend-Listener hinzu und fokussieren im Callback das erste Eingabeelement im Modal. Fertig!

Einschränkungen

Dies ist ein kurzes Experiment, das ich durchgeführt habe, um einen funktionierenden Proof-of-Concept für Fokus-Falle mit der :focus-within-Pseudoklasse zu erstellen. Es hat mehrere Einschränkungen im Vergleich zu dedizierten JavaScript-Lösungen, um dies zu erreichen. Dennoch ist etwas besser als nichts!

Hier sind ein paar Dinge, die dieser Implementierung fehlen

  1. Laut W3C-Richtlinien sollte der Fokus auf dem fokussierbaren Element zirkulieren. Wir fokussieren aber immer auf das erste input-Element. Das liegt daran, dass wir ohne mehr JavaScript nicht wissen können, ob der Fokus vom ersten oder vom letzten Element verloren gegangen ist.
  2. Wir fokussieren immer wieder auf das erste input-Element. Aber es gibt viele weitere fokussierbare HTML-Elemente, die vor dem input vorhanden sein könnten, oder vielleicht gibt es gar kein input-Element im Modal. Auch hier erkennen vollwertige JavaScript-Lösungen und pflegen eine Liste aller fokussierbaren Elemente und fokussieren das richtige.

Bessere (JavaScript) Implementierungen von Fokus-Falle

  1. Wie bereits erwähnt, ist ein funktionierendes Beispiel in der W3C-Dokumentation selbst verfügbar.
  2. Hier ist eine weitere Implementierung von Rodney Rehm, die ebenfalls auf Tabulator reagiert.
  3. Greg Kraus hat eine Bibliothek, die dies erreicht. Seine Implementierung pflegt eine Liste von Selektoren für alle gültigen fokussierbaren Elemente.
  4. Eine weitere leichtgewichtige Bibliothek zur Erstellung zugänglicher Modals.

Das war's für dieses Experiment. Wenn Ihnen dieser Trick gefallen hat, können Sie mir auf Twitter folgen, wo ich weitere Artikel und meine Nebenprojekte teile.