Tastaturfokus setzen und das richtige Timing

Grundlagen

Vor ein paar Tagen hatte ich mal wieder einen netten Austausch mit dem Ansprechpartner für Accessibility der Forensoftware Discourse. Discourse ist eine komplexe Single-Page App, in der ständig Inhalte dynamisch nachgeladen und aktualisiert werden. Das Thema ist nicht Discourse-spezifisch, sondern wahrscheinlich ein verbreiteter Bug, der an sehr vielen Stellen unter dem Radar fliegt. Bevor es zur Sache geht, möchte ich aber noch Betonen, wie sehr ich mich über die Motivation und Bereitschaft freue, mit der meine eingereichten Issues inkl. Lösungsvorschlägen umgesetzt werden. Normalerweise dauert es ungefähr zwei Tage, bis eine Antwort kommt, und eine Lösungsfindung ist bisher auch immer gelungen. Aber ich gebe mir schließlich auch Mühe mit meinen Reports.

Das Problem

Es kommt vor, dass man mit JavaScript Inhalte zu einer Seite hinzufügt oder ändert, um danach den Tastaturfokus dort hinzuziehen. Das könnte z.B. ein Widget sein oder auf ein angesprungeneder Forenpost. Typischerweise wird diese Situation durch Benutzerinteraktion ausgelöst wie auf einen Link oder Button zu klicken.

So weit, so schön die Theorie. Es kann aber vorkommen, dass der Fokus nicht erfolgreich platziert wird und die Seite den Fokus völlig verliert. Das lässt sich testen, indem man direkt im Anschluss Tab drückt. Dieses Verhalten ist wahnsinnig nervig, ein echter UX-Killer.

Im konkreten Fall von Discourse ging es um das gezielte Navigieren zu bestimmten Beiträgen per Link. Immer wenn der Beitrag noch nicht sichtbar ist und erst nachgeladen und gescrollt werden muss, trat dieses Problem auf. Schnell einem Link zu folgen und gleich an der Zielstelle zu landen, ist so nicht möglich.

Auf das timing kommt es an

Der Grund für dieses Fehlverhalten ist, dass der zu fokussierende Inhalt noch nicht fertiggerendert ist, wenn die App versucht, den Fokus zu setzen. Der neue Inhalt ist schon im DOM, aber er muss noch in der visuellen Darstellung ankommen, mit layoutschritten und allem, was dazu gehört. Erst wenn die Darstellung sich stabilisiert hat, kann der Fokus gesetzt werden. Je nach Größe des Inhalts, Grafikleistung des Gerätes, Abstand zwischen Ausgangspunkt und Ziel etc. kann das um die 300 ms dauern ab dem Zeitpunkt, an dem der Inhalt im DOM erstellt wurde.

Leider ist mir kein Browser-Event dafür bekannt, das wäre der sauberste Ansatz. Deswegen lässt sich die zeitspanne nur heuristisch abschätzen. Üblicherweise wird empfohlen, das Fokussieren um etwa 300 ms zu verzögern, nachdem der Inhalt geladen wurde.

// Fix für sauberes Fokussieren, nicht löschen!
setTimeout(() => targetElement.focus(), 300)

Je nach Einzelfall sieht die Implementierung sehr unterschiedlich aus. Es kann sich um ein Custom-Element handeln, das sich selbst initialisiert, aber auch um einen clientseitigen Router.

Präventionsparadoxon

Nach einiger Zeit vergessen wir, welchen Zweck eine nicht so schöne Codestelle hat, warum sie eingefügt wurde. Dann entfernen wir sie im ästhetischen Eifer und das Problem kommt zurück. Bei Discourse scheint genau das vor ungefähr einem Jahr passiert zu sein, denn davor hatte es funktioniert. Das war mir damals sogar positiv aufgefallen, wie gut die Navigation funktioniert hat. Am besten fügt man an solchen Stellen nicht-Löschen-Kommentare ein, so was ist ein echter Grund für Kommentare im Code.

Das Beispiel zeigt aber auch, dass Barrierefreiheit technisch degradiert, wenn niemand mit entsprechender Expertise sie dauerhaft im Blick behält. Ein Problem wurde gelöst und abgehakt, das Projekt entwickelt sich weiter, und irgendwann kommt das Problem dabei wieder zurück. Da muss man kontinuierlich dranbleiben, testen und beobachten, und es muss mindestens ein Teammitglied geben, das über die entsprechenden Kenntnisse verfügt. Ich kann gut verstehen, wenn Entwicklungsteams keine Lust auf externe „Berater“ haben, die nur reinreden und nicht mitarbeiten. Aber dann muss die Kompetenz eben stattdessen ins Team, und für die entsprechende Person muss es möglich sein, ein bisschen Zeit fürs Testen abzuzweigen. Wie sich das mit bestimmten Arbeitsmentalitäten (nicht) verträgt, darüber werde ich mich an anderer Stelle auslassen.