Warenkorb
Sogenannte Warenkörbe stellen eines der Herzstücke eines E-Commerce-Projektes dar. Es ist ein digitale Ort, den Kundinnen und Kunden mit interessanten Waren oder Dienstleistungen befüllen können, bevor sie zur Zahlung und damit auch juristisch zum Abschluss eines Verbrauchervertrags schreiten. Entsprechend wichtig ist es, den Warenkorb zugänglich für alle zu gestalten.
Warenkörbe - Erfahrungswerte
Wenn man sich Warenkorb-Implementationen in typischen Webshops anschaut, so fällt einem eine gewisse Konvention auf. Nicht ungewöhnlich ist folgender Aufbau und folgende Gestaltung:
- Häufig ist ein Auslöser zum Warenkorb oben rechts im Interface, mit einem Einkaufswagen-Piktogramm versehen und gibt per kleiner Zahlanzeige an, wie viel Waren in ihm liegen.
- Klickt man dann dann auf das angesprochene Kontrollfeld, legt sich in vielen Fällen ein Dialog über den Rest der Seite – gerne von rechts, wenn der Auslöser ebenfalls auf der rechten Seite ist. Er „dunkelt“ dann möglicherweise die restlichen Bestandteile des Interfaces oder Shops links ab und listet sowohl die Produkte bzw- Dienstleistungen, die sich in ihm befinden, ihre Anzahl, ihr Einzel- und Gesamtpreis sowie eine Möglichkeit, zum finalen Kaufvorgang zu gehen und somit die Waren oder Dienstleistungen im Korb zu erstehen (der so genannte „(lang: en text: Checkout)“).
- Der Warenkorbinhalt ist dynamisch und aktualisiert in Echtzeit: Ändert man die Menge der einzelnen Bestandteile, passt sich ohne Neuladen der Seite der Preis pro Produktkategorie sowie der Gesamtpreis an.
Von diesem nicht ungewöhnlichen Muster ausgehend, schauen wir uns in diesem Modul einmal an, wie die einzelnen Bestandteile zugänglich, standardkonform und – unter anderem – kompatibel mit Hilfstechnologien wie Screenreadern umzusetzen sind:
Ein empfehlenswertes Warenkorb-Muster
Das typische Muster hat die zwei genannten Bestandteile:
- Eine Art Widget, das auf jeder Seite präsent ist (wie erwähnt, meist oben rechts), in Echtzeit die Menge der Produkte oder Dienstleistungen im Warenkorb angibt und bei Interaktion/Klick die volle Warenkorb-Darstellung öffnet. Ich nenne es im Folgenden der Kürze halber den „Warenkorb-Auslöser“.
- Die komplette Warenkorbdarstellung, die die hereingelegtem Elemente und Ihre Preise listet. Darüber hinaus wird ein Gesamtpreis (mit etwaigen Zwischenpreisen) in Echtzeit berechnet, sobald sich die Inhalte ändern und eine deutliche Möglichkeit geboten, im Kaufprozess weiter zu verfahren (z. B. in Form eines großen Checkout-Buttons, der eine Aufschrift wie „Zur Kasse gehen“ tragen kann). Die komplette Darstellung des Warenkorbs kann per so genanntem modalen Dialog (Warenkorb legt sich über die Seite,„dunkelt“ sie aber ab) und eigener Warenkorb-Seite gelöst sein. Jeweils mehr dazu weiter unten, zum Thema Dialoge im entsprechenden Modul.
In der Beispielimplementation auf ecbf.shop ist das Warenkorb-Konstrukt folgendermaßen umgesetzt:
Der Warenkorb-Auslöser
Da ein auf einen Klick auf „Warenkorb“ (oder ähnlich) eine Interaktion folgt, muss das verwendete Element eine interaktive semantische Rolle haben. Wenn ein Dialog (siehe nächste Sektion) geöffnet wird, ist das ein Element mit der Rolle button, was am einfachsten per HTML-<button>-Element umgesetzt werden kann. Führt ein Klick auf das Kontrollfeld jedoch auf eine eigene Seite oder einen eigenen, per URL erreichbaren (sogenannten) View einer Single Page Application (siehe übernächste Zwischenüberschrift), so sollte es sich um einen Link handeln. Die Rolle link mit all ihren nötigen Eigenschaften ist am besten per <a>-Element (inklusive href-Attribut!) zu realisieren.
Der Warenkorb-Dialog
Wenn sich der Warenkorb, z.B. von der rechten Seite über das Interface schiebt und es dabei abdunkelt und deaktiviert, aber die „auslösende Seite“ bestehen lässt, kann man davon ausgehen, dass der Warenkorb hier als sogenannter „modaler Dialog“ implementiert werden sollte. „Dialog“, weil es ein „Unterfenster“ ist, das sich nachvollziehbar über eine Oberfläche (hier: den Rest der Shop-Benutzeroberfläche) legt und diese teilweise verdeckt. „Modal“, weil es dabei den sprichwörtlichen wie wörtlichen Fokus auf sich zieht und vor allem alle Teile des Interfaces blockiert, die nicht Dialog oder Dialoginhalt sind (es „ändert den Modus“, wird deswegen „modal“ genannt). Weitere Pflicht-Eigenschaften eines modalen Dialogs sind:
- Der Tastatur-Fokus muss beim Auslösen/Öffnen in den modalen Dialog versetzt, werden, beim Schließen muss er entsprechend wieder zurück zu auslösenden Element wandern (das fast immer eine Schaltfläche ist).
- Ist der Dialog offen und wahrnehmbar, so muss der Tastaturfokus auf alle (interaktiven) Inhalte in ihm beschränkt werden. Grund dafür ist, dass der Fokus nicht „hinter“ den Dialog gelangen und damit zumeist unsichtbar werden darf. Zur Erinnerung: „modaler“ Dialog heißt in diesem Zusammenhang so viel wie „ein alles andere blockierender“ Dialog. Entsprechend darf ein Tastaturfokus nicht den „deaktivierten“ Teil einer Oberfläche erreichen.
- Er gibt gegenüber Hilfstechnologien wie Bildschirmvorleseprogrammen (Screenreadern) zu erkennen, dass er eben ein modaler/blockierender Dialog ist. Das geschieht über semantische HTML-Rollen (
dialog), der per JavaScript modal gestartet wurde (.showModal()-Methode) und ARIA-Zustandsbeschreibungen (aria-modal="true").
Die gute Nachricht ist: man muss Dialoge dieser Art und die genannten Funktionen nicht mehr zwingend manuell unter Verwendung von ARIA implementieren, sich z. B. selbst um Rollen, Zustände und Fokusmanagement kümmern (man kann es dennoch, wenn mal will). Stattdessen ist seit relativ kurzer Zeit das <dialog>-Element im HTML-Werkzeugkoffer zu finden und genießt ausreichende Unterstützung. Dieses übernimmt das Fokusmanagement, das Deaktivieren des „Hintergrundes“ und implementiert die erforderlichen Rollen und Zustandsbeschreibungen. Wichtig: um einen Dialog modal zu starten, muss man ihn per JavaScript-Methode .showModal() starten, eine Initiierung mit anderen Mitteln oder auch nur per .show()-Funktionsaufruf führt nur zu einem nichtmodalen Dialog. Wenn ein offener Warenkorb den Rest der Benutzeroberfläche optisch wie programmatisch „in den Hintergrund rücken“ soll, wäre ein nichtmodaler Dialog nicht das Nutzungsmuster der Wahl (sondern ein modaler Dialog, siehe entsprechendes Modul).
Unabhängig davon, mit welchen Mitteln der Dialog gebaut wurde, ist es angemessen, ihm einen programmatischen Namen zu geben. Wenn eine Website mehrere mögliche Dialoge hat (z. B. einen weiteren für Cookiehinweise), kann man vor allem nicht-visuell diese am ehesten auseinanderhalten, wenn sie Namen wie „Warenkorb“ oder „Cookiebanner“ tragen. Um einen Dialog also zu benennen, muss leider doch wieder auf ein ARIA-Attribut zurückgreifen: Das ist entweder aria-label, also z. B. aria-label="Warenkorb". Oder aber eine z. B. wahrnehmbare Überschrift wird als „Namensgeber“ referenziert. Dazu nutzt man aria-labelledby und gibt als Attribut-Wert die ID des Textes an, der den Namen des Dialogs bereithält. Ein Beispiel:
<dialog aria-labelledby="title">
<h1 id="title">Warenkorb</h2>
<!-- Sonstiges... -->
</dialog>
Auch in diesem Szenario ist der Dialog nun mit dem Namen „Warenkorb“ ausgestattet.
In der ecbf.shop-Beispielimplementierung findet sich die Benennung in Zeile 12 des warenkorb-Partials.
Alternativen: Eigene Seite oder SPA-Ansicht
Falls der Warenkorb kein Unterfenster eines anderen HTML-Dokumentes ist, sondern eher eine eigene Unterseite , gelten hier die Basis-Regeln der Barrierefreiheit (z. B. was eine nachvollziehbare Überschriftenstruktur und ein sinnvoller Dokumententitel wie „Warenkorb“ angeht).
Löst man den Warenkorb als „gefühlt“ eigene Seite, ist es aber in Wirklichkeit kein „klassisches“ Dokument sondern ein so genannter „View“ (in etwa: Web-App-Zustand) in einer Single Page Application (SPA) ist, sollte man auch hier…
- …auf einen klaren und aussagekräftigen Dokumententitel achten (übrigens lässt sich dieser per JavaScript leicht dynamisch setzen, wie folgt:
document.title = "Neuer Titel!"). - …sicher gehen, dass der Warenkorb – auch „nur“ als View – unter einer eigenen URL erreichbar ist. Hierzu gibt es zwar keine harte Anforderung in Barrierefreiheitsrichtlinien, und die Maßnahme geht mehr in die Richtung „besseres Nutzungserlebnis“, dennoch gehen Besuchende häufig einfach von einer Dokumenten-artigen Erfahrung aus – vor allem, wenn der Warenkorb als „gefühlt eigene Seite“ gestaltet ist, also z. B. unter einer eigenen URL aufrufbar ist.
Warenkorb-Inhalte
Unabhängig von den Seiten oder Unterseitenkonzept stellt sich nun die Frage, wie man die Inhalte im Warenkorb am Sinnvollsten strukturiert. Haben Produkte Eigenschaften wie Bestellnummern, Namen und Einzelpreise und Verpackungs-Optionen, so ergibt es Sinn, diese in tabellarischer Form zu strukturieren. Jede Tabellenzeile wäre dann eine Ware oder Dienstleistung (mit Stückzahlen, wo es Sinn ergibt), und die Tabelle insgesamt der Warenkorb. Unterhalb der Tabelle steht dann der in Echtzeit berechnete Gesamtpreis:
Auch hierzu bietet ecbf.shop eine Beispielimplementation an, unter dieser URL startet der Shop mit einem vorausgefüllten und offenem Warenkorb.
Was bestimmte Werte angeht, sei es Stückzahl oder Preis, so werden beide in Echtzeit berechnet und aktualisiert angezeigt. Wenn man davon ausgeht, dass man in bestimmten Tabellenzeilen die Produktstückzahlen ändern kann (z. B. per input[type="number"]), so sind keine gesonderten Hinweise für Screenreader und ihre Nutzer*innen nötig (die sonst Echtzeit-Updates dieser Art nicht unmittelbar mitbekommen). Bestimmte Ereignisse, wie das grundsätzliche Hinzufügen zu oder Entfernen aus dem Warenkorb sollten dennoch jeweils mit einer sogenannten asynchronen Anmerkung an den Screenreader vonstattengehen. Mehr dazu im nächsten Abschnitt.
Status-Ereignisse
Für Screenreader sind Echtzeit-Updates (also Aktualisierungen eines Dokuments, ohne das ein Neuladen stattfindet) tendenziell problematisch. Es besteht die Gefahr, dass die Nutzenden dieser Bildschirmvorleser wichtige Updates auf der Seite nicht oder nur nach manuellem Suchen bemerken.
Aus diesem Grund bietet der ARIA-Werkzeugkasten die so genannten „Live-Regions“, die gerade im Zusammenspiel mit einem Warenkorb als modaler Dialog Sinn ergeben. Wo ein Screenreader sonst im HTML-Dokument (oder dem „live“ erzeugten HTML) Element pro Element voranschreitet und immer nur genau das, was vor seinem „virtuellen Cursor“ liegt, vorließt – so bieten Live-Regions die Möglichkeit Statusmeldungen abzugeben, die nichts mit der aktuellen Position des virtuellen Cursors zu tun haben. Per HTML gibt es einige Wege, eine Live-Region-Ausgabe zu erzeugen:
- Man ergänzt in einem Element das Attribut
aria-live, z. B. mit dem Wertpolite– und schon wird es auf textliche Änderungen überwacht. Bekommt der Screenreader mit, dass ein „überwachtes“ Element seinen Text geändert hat, gibt er je nach Einstellung den neuen oder gesamten Text aus. Mitaria-atomicwiederum kann man bestimmen, ob der Screenreader den Text des gesamten Containers (Werttrue) oder nur den just geänderten Text (Wertfalse) ausgibt. - Alternativ gibt man einem Element die Rolle
statusund erzeugt damit einerseits die semantische Information, dass es sich bei diesem Element und seinem Text um eine Statusnachricht handelt. Gleichzeitig ist ein Element mitrole="status"auch eine Live-Region. - Für z. B. Berechnungsergebnisse, die auch als Live-Region ausgegeben werden sollen, ist das
<output>-Element gut geeignet. Ganz abstrakt markiert es aber nur eine wie auch immer geartete Ausgabe von etwas (als Gegenstück zur Eingabe,<input>). Auch<output>hat die Eigenschaft einer Live-Region, auch dieses Element wird „überwacht“ und Textänderungen per JavaScript in ihm „bemerkt“. Und ähnlich wie ein<input>- benötigt auch ein<output>-Element einen zugänglichen Namen bzw. eine programmatische Beschriftung.
Patrick H. Laucke hat kürzlich einen tiefergehenden Artikel zu Live-Regions, ihren theoretischen und auch praktischen Einsatz geschrieben (hier das englischprachige Original).
Im Rahmen folgender Ereignisse ergibt eine Live-Region-Ausgabe Sinn:
Hinzufügen
Wenn ein Produkt oder mehrere Produkte zum Warenkorb hinzugefügt werden, ist folgender Ablauf zu empfehlen:
- Ein Live-Region-Aufruf wie „Artikel 'Hippe Jeans' ergänzt.“ An dieser Stelle ist es auch sinnvoll, über den neuen Gesamtpreis des Warenkorbs zu informieren, der durch das Hinzufügen der Ware angepasst wurde.
- Parallel dazu wird der Dialog mit dem Warenkorb geöffnet. Wurde das
<dialog>-Element benutzt, so kümmert sich der Browser um das Versetzen des Tastaturfokus'. - Der virtuelle Cursor des Screenreaders bzw. der Tastaturfokus befindet sich nun im offenen Warenkorb-Dialog und der/die Nutzende kann mit ihm interagieren.
Menge und Optionen ändern
Hier gehen wir von einer Implementierung aus, bei der ein <input type="number"> die genaue Eingabe der Stückzahl eines Produktes ermöglicht.
Da es sich hier um eine Wertänderung in einem Eingabefeld handelt ist keine Live-Region-Ausgabe nötig. Wohl aber eine Neuberechnung des Gesamtpreises, über den Screenreadernutzende wiederum per Live-Region informiert werden sollten: „Neuer Gesamtpreis des Warenkorbs: € 40.46“.
Ähnliches gilt auch für Produktoptionen wie Geschenkverpackungen – hat deren Buchung einen Echtzeit-Einfluss auf die Preise ist dies ein Job für Informationen per Live-Region-Ausgabe.
Entfernen
Für das Entfernen eines Produkts oder einer Produktart wird ein Live-Region-Aufruf wie „Artikel 'Hippe Jeans' entfernt“ empfohlen (oder die Ausgabe einer Fehlermeldung, sollte es aus irgendwelchen Gründen nicht möglich sein). Hier bietet sich ebenfalls eine Information an, welcher neue Gesamt-Warenkorb-Preis in der Folge entsteht. Ob sich der Warenkorb-Dialog in diesem Szenario ebenfalls schließt, sollte davon abhängen, ob die just entfernte Ware der letzte Eintrag der letzte verbleibende im Shopping Cart war – und dieser nun leer ist.
Wenn wir eine Zeile einer Datentabelle dynamisch entfernen, entsteht aber ein Problem: Wohin mit dem Fokus? Mit dem Löschen der Zeile entfernen wir auch alle Fokushaltepunkte in ihm, und auf einem dieser (der „Enfernen“-Schaltfläche) lag der Tastaturfokus bisher, wenn wir von einer Nutzung per Tastatur ausgehen. Löschen wir also letztendlich das aktuell aktive HTML-Element und werfen den Fokus versehentlich auf den body des Elements zurück?
Um das zu vermeiden, wird die Datentabelle des Warenkorbs fokussiert. Dies geschieht nur ausnahmsweise, weil ein Fokussieren von nicht-interaktiven Elementen normalerweise vermieden werden sollte. Man kann ein Element programmatisch, aber nicht manuell fokussierbar machen, in dem man das Attribut tabindex="-1" auf ihm verwendet. Somit ist die Tabelle durch ein Skript fokussierbar, aber nicht durch einen menschlichen Nutzenden versehentlich erreichbar.
Zu guter Letzt erinnern wir uns an die Grundregel, dass fokussierbare Elemente auch einen zugänglichen Namen benötigen. Weil wir eine Tabelle fokussieren wollen, sollten wir uns damit auseinandersetzen, wie wir diesen Elementen einen zugänglichen Namen geben können. Um es abzukürzen: Dazu gibt es das <caption>-Element. Dieses verbergen wir visuell, um eine Doppelung mit den Informationen im Tabellenfuß zu vermeiden, und befüllen es dynamisch mit dem jeweiligen Gesamtpreis:
<caption class="visually-hidden">
<span>Warenkorb, Gesamtsumme </span>
<span data-cart-caption-total="">5.99</span>
<span> Euro</span>
</caption>
Etwas ausführlicher wird dieser Ansatz im Modul zur Tastaturbedienung erklärt.
Eine zugängliche Beispielimplementierung eines Warenkorbs als Dialog inklusive aller sinnigen Live-Region-Ausgaben, nötigem Fokusmanagement und dynamischer Tabelleninformationen findet sich auf ecbf.shop.
Wer sich in das Thema Live-Regionen über oben stehenden Link hinaus weiter vertiefen will, dem sei dieser Artikel ans Herz gelegt.
Übungen
- Wenn ein Warenkorb in einer Art „Unterfenster“ erscheint, keine eigene Seite mit URL ist und alles außer dem Unterfenster abdunkelt, welches Nutzungs-Pattern ist hier empfehlenswert?
- Wie nutzt man das
<dialog>-Element, um einen modalen Dialog zu erzeugen? - Nennen Sie mindestens einen Vorteil des nativen
<dialog>-Elements. - Mit welchen Problemen haben Screenreader-Nutzende zu rechnen, wenn in einem Interface viele Aktionen (wie z. B. das Befüllen eines Warenkorbs) per asynchronem JavaScript, also ohne das Neuladen einer Seite passiert?
Zusammenfassung
- Wenn ein Warenkorb modalen Charakter hat, das heißt: ein Unterfenster ist, das den Rest der Oberfläche „deaktiviert“, dann sollte er als modaler Dialog umgesetzt werden
- Häufig ist eine Datentabelle der beste Weg, Daten eines Warenkorbs strukturiert wiederzugeben
- Wenn der Warenkorbinhalt (und damit die Tabelle) dynamisch verändert wird – zum Beispiel Tabellenzeilen entfernt werden – so muss sicher gestellt werden, dass der oder die Screenreader-Nutzende per ARIA-Live-Region über die Änderung informiert wird, sowie der Tastaturfokus nicht verloren geht. Im Beispiel-Dummy findet sich eine Variante, bei der die Tabelle selbst nach Löschen einer ihrer Zeilen programmatisch fokussiert wird.