Skip to content

Adding keyboard interaction

Hier lernen wir drei Dinge:

  • Fokus verbessern
  • die Standard-Tab-Reihenfolge ändern
  • mit den Pfeiltasten zwischen den Tabs wechseln

Wenn der Fokus nicht genug Kontrast hat, müssen wir den Fokus-Style ändern (das machen wir aber sowieso in a11y!). Eine MÖglichkeit ist ein innerer box-shadow. Die Fokus-Darstellung sollte konsistent sein, d.h., alle fokussierbaren Elemente sollen denselben Style bekommen (daher *). Tatsächlich funktioniert das hier nicht, da der innere box-shadow sich über Links legt. Daher wenden wir ihn tatsächlich nur auf .tab an:

*:focus {
outline: none;
box-shadow: 0 0 0 6px lightskyblue;
}
.tab:focus {
box-shadow: inset 0 0 0 6px lightskyblue;
}

User erwarten, dass sie auf eine bestimmte Art durch tabbed components tabben können:

  1. Tab -> gehe zum gewählten Tab
  2. Tab -> gehe, wenn vorhanden, zum ersten fokussierbaren Element innerhalb des Tab-Contents
  3. Tab -> gehe zum nächsten fokussierbaren Element, z.B. zum nächsten Link oder zum nächsten Tab oder, wenn es keinen Tab mehr gibt, zum nächsten fokussierbaren Element nach dem Tabby

Im Moment funktioniert das bei uns noch nicht. Wir tabben mit der Tab-Taste durch die inaktiven Tabs. Um zu verhindern, dass nicht aktive Tabs mit der Tab-Taste erreicht werden, müssen wir die roving tabindex methode anwenden:

  1. Wir setzen tabindex="-1" für Elemente, in die wir nicht reintabben sollen
  2. Wir entfernen tabindex für Elemente, die mit der Tabtaste erreichbar werden sollen

Als erstes setzen wir tabindex="-1" im HTML für die inaktiven Tabs:

<div class="tabs">
<button ... class="tab is-selected">Tab 1</button>
<button ... class="tab" tabindex="-1">Tab 2</button>
<button ... class="tab" tabindex="-1">Tab 3</button>
</div>

Die Tab-Reihenfolge verhält sich nun wie erwartet. Wenn allerdings nun auf den zweiten Tab geklickt wird, muss dieser Tab einen normalen Tabindex bekommen und der erste Tab deaktiviert werden. Das bedeutet, wir

  1. setzen `tabindex=“-1” für die anderen Tabs und
  2. entfernen den tabindex vom geklickten Tab.
tabsList.addEventListener('click', e => {
// ...
// selects a tab
tabs.forEach(t => {
t.classList.remove('is-selected');
t.setAttribute('tabindex', '-1'); // disables other tabs
});
tab.classList.add('is-selected');
tab.removeAttribute('tabindex'); // enables current tab
})

Nun sind die anderen Tabs nicht mehr mit der Tastatur erreichbar. Dieses Verhalten müssen wir jetzt mit den Pfeiltasten implementieren.

  • Pfeil nach links: vorhergehender Tab wird selektiert
  • Pfeil nach rechts: nächster Tab wird selektiert

Die fünf Schritte, um dieses Verhalten zu implementieren:

  1. auf ein keydown Event lauschen
  2. prüfen, ob die Pfeil nach rechts oder nach links Taste gedrückt wurde
  3. wenn Pfeil nach rechts, den nächsten Tab finden
  4. wenn Pfeil nach links, den vorhergehenden Tab finden
  5. den Tab selektieren

Zuerst lauschen wir auf ein keydown Event auf der Tab-Liste und wenn das Event weder die Pfeil-nach-rechts noch die Pfeil-nach-links Taste waren, machen wir ein early return:

tabsList.addEventListener('keydown', e => {
// analog zu const target = e.target;
// const key = e.key;
const { key } = e;
if (key !== 'ArrowLeft' || key !== 'ArrowRight') return;
// do sth.
})

Als nächstes müssen wir schauen, welcher Tab aktuell ausgewählt wurde. Der soeben angeklickte Tab hat die Klasse is-selected. Wir suchen also mit contains einen Tab, dessen classList die Klasse is-selected enthält. Von diesem Tab wollen wir den Index, dazu nutzen wir findIndex:

tabsList.addEventListener('keydown', e => {
// ...
const index = tabs.findIndex(t => t.classList.contains('is-selected'));
})

Ist der index 0, ist der User auf dem ersten Tab: tabs[0] Ist der index gleich tabs.length -1, ist der User auf dem letzten Tab: tabs[length - 1]

Wenn die Pfeil-nach-rechts Taste gedrückt wurde und der User ist nicht auf dem letzten Tab, dann selektieren wir den nächsten Tab. Der nächste Tab ist tabs[index + 1].

tabsList.addEventListener('keydown', e => {
// ...
const index = tabs.findIndex(t => t.classList.contains('is-selected'));
if (key === 'ArrowRight' && index !== tabs.length - 1) {
const nextTab = tabs[index + 1];
console.log(nextTab);
}
});

Jetzt haben wir zwei Möglichkeiten:

  • Wir können den Code zum selektieren des nächsten Tabs einfach schreiben bzw. aus dem Click-Event-Listener kopieren, was bedeutet, dass wir viel doppelten Code bekommen.
  • Wir können JavaScript auf den Tab “klicken” lassen! Ja, JavaScript hat eine click()-Methode. Alles weitere erledigt dann unser click EventListener.
tabsList.addEventListener('keydown', e => {
// ...
const index = tabs.findIndex(t => t.classList.contains('is-selected'));
if (key === 'ArrowRight' && index !== tabs.length -1) {
const nextTab = tabs[index];
nextTab.click();
}
});

Wenn die Pfeil-nach-links Taste gedrückt wurde und der User ist nicht auf dem ersten Tab, dann selektieren wir den vorhergehenden Tab. Der vorhergehende Tab ist tabs[index - 1].

tabsList.addEventListener('keydown', e => {
// ...
if (key === 'ArrowLeft' && index !== 0) {
const previousTab = tabs[index - 1];
previousTab.click();
}
});

Wir können den Code noch ein bisschen eleganter machen:

tabsList.addEventListener('keydown', e => {
// ...
let targetTab;
if (key === 'ArrowLeft' && index !== 0) targetTab = tabs[index - 1];
if (key === 'ArrowRight' && index !== tabs.length -1) targetTab = tabs[index + 1];
if (targetTab) targetTab.click();
});