Gestalte ein barrierefreies News-Karussell als Angular Standalone Component

Ich liebe Karussells! Die bunten Freizeitpark-Attraktionen mit Tierfiguren und Wägelchen sind oft wunderschön gestaltet. Kinder können Stunden über Stunden damit verbringen und sich im Kreis drehen. Die pure Freude!

Kann ich dasselbe über Karussell-UI-Komponenten sagen? Nicht wirklich. Ich mag die meisten Web-Karussells nicht. Vor allem, wenn sie automatisch den Inhalt wechseln. Außerdem sind die meisten von ihnen ein Accessibility-Albtraum. Also habe ich mir gedacht: Das geht doch besser!

Ein Karussell bei Nacht mit hellen Lichtern. Die Figuren eines Pferdes und eines Tigers sind zu sehen. Foto: © Alexander Nadrilyanski / pexels.com

Ich habe ein News-Karussell als eigenständige Angular-Komponente erstellt. Die Komponente ist barrierefrei, responsiv und bietet flüssige Animationen. Werfen wir einen Blick darauf, wie es aufgebaut ist und funktioniert.

Demo: Barrierefreies News-Karussell

Meine Demo-Anwendung zeigt eine Liste von Nachrichten an, jede mit Überschrift, einem Textausschnitt und einem Hintergrundbild. Die Nutzer:innen können sich mit Wischgesten oder den Navigations-Schaltflächen von Nachricht zu Nachricht bewegen.

Anforderungen an Design und Barrierefreiheit

Es gibt kein natives HTML-Element für Web-Karussells. Deshalb gibt es so viele verschiedene Implementierungen. Zumindest hat das W3C in seinem „ARIA Authoring Practices Guide“ grundlegende Anforderungen definiert. Das Karussell-Pattern lautet:

A carousel presents a set of items, referred to as slides, by sequentially displaying a subset of one or more slides. Typically, one slide is displayed at a time, and users can activate a next or previous slide control that hides the current slide and “rotates” the next or previous slide into view.

Diese abstrakte Definition gilt sowohl für einfache Bild-Rotatoren als auch für Diashows mit komplexem Inhalt. Im Allgemeinen sollten Web-Karussells die folgenden Anforderungen erfüllen:

  1. Es sollte assistiven Technologien seine Struktur vermitteln.
  2. Es sollte den aktuell aktiven Inhalt anzeigen, sowohl visuell als auch für assistive Technologien.
  3. Die Nutzer:innen sollten nicht gezwungen sein, das Karussell mit Wischgesten zu bedienen. Es sollte simple Steuerelemente bieten, die man anklicken oder mit der Tastatur bedienen kann.
  4. Screenreader-Nutzer:innen sollten über dynamische Inhaltsänderungen (z.B. der Wechsel zur nächsten Slide) mittels Statusmeldung informiert werden.
  5. Wenn das Karussell den angezeigten Inhalt automatisch wechselt, sollte es auch eine Schaltfläche zum Anhalten und erneuten Starten der Rotation haben.

Ich hasse Bewegungen und Animationen, die automatisch starten. Deshalb wechselt der angezeigte Inhalt in meinem News-Karussell nicht von selbst. Somit braucht es auch keine Pausentaste.

Struktur und semantische Auszeichnung

Der Karussell-Container

Die ARIA-Spezifikation enthält keine carousel Rolle. Daher müsst ihr role="region" definieren und die benutzerdefinierte Beschreibung aria-roledescription="Karussell" bereitstellen. Ein verständliches Label wird durch das Attribut aria-label bereitgestellt.

In meiner NewsCarouselComponent nutze ich HostBinding, um diese Werte für den Container der Karussell-Komponente zu setzen:

/** * The aria label for the carousel container. * It should convey to screenreader users what the carousel is about. */ @HostBinding('attr.aria-label') @Input() public carouselLabel!: string; @HostBinding('attr.role') role = 'region'; @HostBinding('attr.aria-roledescription') get carouselDescription() { return this.config.carouselDescription; }

Das Karussell enthält zwei Icon-Schaltflächen, um zur vorherigen oder nächsten Slide zu navigieren. Programmatisch ermittelbare Labels werden mit dem Attribut aria-label für die button-Tags definiert.

Der Slide-Container

Für jeden Slide-Container ist role="group" und aria-roledescription="Slide" gesetzt. Das Attribut aria-label des Containers gibt die Nummer der Slide und die Gesamtanzahl an (z.B. „2 von 5“).

<div *ngFor="let item of newsItems; let i = index" class="news-item" role="group" [attr.aria-roledescription]="config.slideDescription" [attr.aria-label]="(i + 1) + ' ' + config.slideLabel + ' ' + newsItems.length" > <!-- slide content --> </div>

Der Nachteil von benutzerdefinierten Rollenbeschreibungen ist, dass sie nicht automatisch von Screenreadern übersetzt werden. Wenn ihr eine mehrsprachige Website erstellt, müsst ihr dies berücksichtigen. Meine NewsCarouselComponent hat einen Input-Wert config mit Standardwerten in Englisch, die überschrieben werden können.

@Input() public config: NewsCarouselConfig = { carouselDescription: 'carousel', slideDescription: 'slide', slideLabel: 'of', nextButtonLabel: 'Next slide', previousButtonLabel: 'Previous slide', }

Vermeide Listen-Elemente

Häufig werden Slides als Liste ausgezeichnet. Screenreader lesen die Anzahl der Elemente in einer Liste vor, ignorieren aber ausgeblendete Listenelemente. In meiner Komponente ist nur die sichtbare Slide auch für assistive Technologien zugänglich. Die anderen Slides werden visuell und programmatisch mit der CSS-Eigenschaft visibility: hidden verborgen.

Wenn ich alle Slides als <li>-Elemente innerhalb einer ungeordneten Liste (<ul>) implementiert hätte, würde der Screenreader das Vorhandensein einer Liste mit nur einem Element verkünden. Das entspricht nicht der tatsächlichen Anzahl an Slides und wäre für die Nutzer:innen verwirrend.

ARIA Live-Region

Das Karussell sollte dynamische Inhaltsänderungen über Statusmeldungen an Screenreader-Nutzer:innen kommunizieren. Zunächst habe ich versucht, das Container-Element für alle Slides zur ARIA Live-Region zu machen. Das Ergebnis war nicht optimal.

Aufgrund der Implementierung der Slide-Animation mit Hilfe der CSS-Eigenschaft visibility las der Screenreader die Überschrift der neuen Slide mehrmals vor oder las die ausgeblendete Slide erneut vor.

Ich habe verschiedene Ansätze mit Eigenschaften wie aria-busy und aria-relevant ausprobiert. Sie funktionierten immer nur bei einigen Browser- und Screenreader-Kombinationen. Daher entschied ich mich schließlich für den einfachen Ansatz, eine visuell verborgene ARIA Live-Region an das Ende des Karussell-Containers zu setzen. Das ist zwar ein bisschen redundant, aber es funktioniert!

<div class="active-slide-live-region" aria-live="polite" > {{config.slideDescription + ' ' + (activeSlideIndex + 1) + ' ' + config.slideLabel + ' ' + newsItems.length + ':'}} {{newsItems[activeSlideIndex].heading}} </div>

Tastaturbedienung und Swipe-Gesten

Ich liebe wischen! Wenn ich online auf ein Karussell stoße, versuche ich instinktiv, über den Bildschirm zu wischen. Swipe-Gesten an sich sind nicht böse. Aber ein Web-Karussell, das nur durch Wischgesten bedienbar ist, schon!

Für viele Menschen mit motorischer Beeinträchtigung sind Wischgesten nicht barrierefrei. Sie bevorzugen einfach klickbare Alternativen wie eine Schaltfläche. Außerdem benötigen Tastatur-Nutzer:innen Steuerelemente, die sie mit der Tabulatortaste ansteuern und mit der Leer- oder Eingabetaste aktivieren können.

Deshalb versuche ich, alle glücklich zu machen! Meine Angular-Komponente implementiert Wischgesten mit der Hammer.js-Bibliothek und enthält auch Schaltflächen, um zur vorherigen oder nächsten Slide zu navigieren.

<div class="news-carousel-controls"> <button type="button" [attr.aria-label]="config.previousButtonLabel" (click)="this.showPreviousSlide()" > <!-- arrow icon --> </button> <button type="button" [attr.aria-label]="config.nextButtonLabel" (click)="this.showNextSlide()" > <!-- arrow icon --> </button> </div>

Slide-Übergänge mit CSS-Animationen

Ich wollte schöne Übergänge zwischen den Slides mit sanften Ein- und Ausblend-Animationen erstellen. Angular bietet ein eigenes Animationsmodul. Mir war jedoch wichtig, die Build-Größe klein zu halten und gleichzeitig effizient zu sein. Deshalb habe ich mich für CSS-Animationen entschieden.

Das Erstellen von Animationen mit CSS ist super einfach und elegant. Zuerst definiert ihr Keyframes für eure benannten Animationen. Jeder Keyframe beschreibt, wie das animierte Element zu einem bestimmten Zeitpunkt während der Animationssequenz dargestellt werden soll.

// Keyframes for slide animation from left to right @keyframes slide-in-from-left { 0% { transform: translateX(-100%); } 100% { transform: translateX(0%); } } @keyframes slide-out-to-right { 0% { transform: translateX(0%); visibility: visible; } 100% { transform: translateX(100%); visibility: hidden; } }

Dann wendet ihr diese Animationen mithilfe der CSS-Eigenschaft animation auf bestimmte Slides an. Auf diese Weise könnt ihr eine Vielzahl von Animations-Eigenschaften (z.B. Name, Dauer, Richtung) in einer einzigen CSS-Regel definieren.

app-news-carousel .news-carousel-items.animate-from-left .news-item { &.active { animation: slide-in-from-left 0.5s forwards; } &.moved-out { animation: slide-out-to-right 0.5s forwards; border-left: 0.125rem solid white; } }

In dem obigen Beispiel werden zwei HTML-Elemente animiert: Das Element mit der Klasse active gleitet von links ins Bild, während das andere Element mit der Klasse moved-out nach rechts aus dem Bild gleitet.

Responsives Design und benutzerdefiniertes Styling

Flexibles Layout mit max-width und Container Queries

Wenn ihr die Karussell-Komponente verwendet, könnt ihr die CSS-Variable --news-carousel-max-width setzen, um eine Basisbreite für den Karussell-Container zu definieren. Wenn keine Variable gesetzt ist, wird der Fallback-Wert der var()-Funktion verwendet.

app-news-carousel { height: var(--news-carousel-height, 25rem); width: var(--news-carousel-max-width, 50rem); max-width: 100%; }

Mithilfe von max-width: 100% stellen wir sicher, dass die Breite des Containers durch den verfügbaren Raum seines übergeordneten Elements begrenzt wird. Auf diese Weise beansprucht das Karussell nie mehr Platz als die Viewport-Breite des Geräts.

Außerdem verwende ich CSS Container Queries, um das Styling des Karussells anzupassen, wenn die Breite des Containers unter einem bestimmten Schwellenwert liegt. Dieses neue, mächtige Feature wird bald von allen modernen Browsern unterstützt werden. Ich werde in einem separaten Blog-Beitrag näher darauf eingehen.

@container news-carousel (max-width: 28rem) { app-news-carousel .news-item .slide-text { bottom: 0.5rem; border-radius: 0.5rem; width: calc(100% - 1rem); p { display: none; } } }

Eigenes Styling mit CSS-Variablen

CSS-Variablen sind ideal für Komponenten-Bibliotheken. Sie können ein Standard-Styling definieren und den Nutzer:innen erlauben, bestimmte Werte mit den entsprechenden Variable zu überschreiben. Eine Applikation kann die folgenden Styles meiner NewsCarouselComponent über CSS-Variablen anpassen:

  • --news-carousel-height: Die Höhe des Karussells.
  • --news-carousel-max-width: Legt die maximale Breite des Karussells fest. Die tatsächliche Breite des Karussells ist abhängig von seinem Container.
  • --news-carousel-button-background: Der Hintergrund (Farbe) der Navigations-Schaltflächen.
  • --news-carousel-button-color: Die Farbe des Pfeilsymbols in den Navigations-Schaltflächen.
  • --carousel-text-background: Der Hintergrund (Farbe) des Textcontainers der Slides.
  • --carousel-text-color: Die Schriftfarbe des Textcontainers der Slides.
  • --news-carousel-slide-focus-color: Die Farbe des Fokusindikators der Slides.

Was es noch zu sagen gibt

Ich möchte eines klarstellen: Meine Karussell-Komponente stellt nur eine von mehreren Möglichkeiten dar, ein barrierefreies, responsives Karussell zu implementieren. Natürlich bin ich davon überzeugt, dass meine Implementierung einfach großartig ist. Aber natürlich gibt es auch andere valide Ansätze.

Web-Standards, Browser und assistive Technologien entwickeln sich ständig weiter. In ein paar Jahren oder vielleicht sogar in ein paar Monaten könnte es also eine bessere Lösung geben. Ich kann es kaum erwarten, sie zu sehen! 😊

Nützliche Links

Erstellt am