SSS - Einheitlicher Standard für Slider

SSS - Einheitlicher Standard für Slider
Standards

35

10 min.

Definitionen

Der SSS-Standard legt Anforderungen an die Semantik, Barrierefreiheit und Funktionslogik von Slidern fest.

Die Anforderungen richten sich nach dem Verhalten der jeweiligen Komponente: endliche oder zyklische Navigation, manuelles oder automatisches Weiterschalten sowie die Anzahl gleichzeitig sichtbarer Slides.

In den Implementierungsbeispielen wird Embla Carousel mit dem React-Wrapper verwendet. Die Bibliothek dient als Beispiel und ist keine vorgeschriebene Abhängigkeit; die API der Beispiele entspricht Embla v8.

Struktur und Semantik

  • Für den Wechsel zwischen Slides werden native <button>-Elemente verwendet. Links dienen der Navigation zu einer anderen Adresse, nicht der Steuerung eines Sliders.
  • Bei einem Slider mit endlicher Reihenfolge ist die Zurück-Schaltfläche am ersten Snap-Punkt und die Weiter-Schaltfläche am letzten deaktiviert. Bei zyklischer Navigation bleiben beide verfügbar, sofern die Komponente den Übergang über die Grenzen unterstützt.
  • Der native Zustand einer nicht verfügbaren Schaltfläche wird mit disabled angegeben. Zusätzlich muss sie sich visuell vom aktiven Zustand unterscheiden, etwa durch eine angepasste opacity und den Cursor not-allowed. Eine rein visuelle Kennzeichnung vermittelt den Zustand nicht an assistive Technologien.
  • Pfeile und Auswahlpunkte für einzelne Slides werden ausgeblendet, wenn alle Elemente in den Viewport passen und kein Wechsel zwischen Positionen erforderlich ist. Maßgeblich ist der tatsächliche Überlauf, nicht allein die Gesamtzahl der Slides.
  • Ein Überlauf wird bei der Initialisierung und bei Größenänderungen des Viewports oder Inhalts geprüft. Geeignet sind Messungen von Container und Slide-Leiste, zum Beispiel mit ResizeObserver; ein Listener nur für window.resize reicht nicht aus, da sich Elementgrößen auch ohne Änderung des Fensters ändern können.
  • Nach Datenaktualisierungen, dem Laden von Schriftarten oder Änderungen des responsiven Layouts wird der Überlauf erneut geprüft, sofern sich dadurch die Abmessungen der Slides ändern.
  • In Embla wird der Zustand der Zurück- und Weiter-Schaltfläche bei der Initialisierung sowie bei den Events select und reInit mit canScrollPrev() und canScrollNext() synchronisiert. Die Schaltflächen werden außerhalb des Viewport-Elements platziert, das Drag-Eingaben verarbeitet.

Responsives Verhalten

  • Die Anzahl sichtbarer Slides und der gap richten sich nach der verfügbaren Breite der Komponente. Je nachdem, ob das Layout auf die Viewport- oder die Breite des übergeordneten Containers reagiert, eignen sich Media Queries oder Container Queries.
  • Auf schmalen Bildschirmen kann ein teilweise sichtbarer benachbarter Slide, etwa 10–20 px, als Hinweis auf weitere Inhalte dienen. Dies ist eine Gestaltungsempfehlung und kein fester Wert; der sichtbare Anteil richtet sich nach Containerbreite, Abständen und Schreibrichtung.
  • Ein teilweise sichtbarer Slide darf keine Steuerelemente überdecken, wichtigen Text abschneiden oder horizontalen Überlauf der gesamten Seite verursachen.
  • Bewegungsrichtung, Reihenfolge der Elemente und Navigationsbeschriftungen müssen der Leserichtung der Oberfläche entsprechen. In RTL-Oberflächen sind die Logik von „zurück“ und „weiter“ sowie die visuelle Richtung der Pfeile zu prüfen.

Barrierefreiheit und Interaktivität

Semantik und zugängliche Namen

  • Der Slider-Container erhält role="region", wenn er einen bedeutenden Seitenbereich darstellt, oder role="group", wenn kein eigener Landmark-Bereich erforderlich ist. aria-roledescription kennzeichnet den Komponententyp mit einer lokalisierten Beschreibung.
  • Der Container erhält einen zugänglichen Namen über eine sichtbare Überschrift und aria-labelledby oder, falls keine sichtbare Überschrift vorhanden ist, über aria-label. Bei Verwendung von aria-roledescription muss das Wort „Karussell“ nicht im zugänglichen Namen wiederholt werden.
  • Jeder Slide wird mit role="group" und einem lokalisierten Wert für aria-roledescription ausgezeichnet und erhält einen aussagekräftigen Namen: über seine Überschrift oder, wenn keine eigene Überschrift vorhanden ist, über seine Position, etwa „2 von 5“. Bei gesetztem aria-roledescription wird das Wort „Folie“ nicht im Namen wiederholt.
  • Navigationsschaltflächen erhalten zugängliche Namen, zum Beispiel aria-label="Vorheriger Slide" und aria-label="Nächster Slide". Sichtbarer Schaltflächentext kann den zugänglichen Namen ohne aria-label bereitstellen.
  • Schaltflächen zur Auswahl eines Slides oder einer Slide-Gruppe werden gruppiert; die Gruppe erhält einen zugänglichen Namen. Für den aktiven Auswahlpunkt wird die Semantik des gewählten Musters verwendet: aria-disabled="true" für eine Gruppe von Auswahlbuttons nach APG oder aria-selected="true" bei einer Tabs-Implementierung. aria-current="true" wird nicht automatisch auf den Slide-Container gesetzt; die aktuelle Position wird am Steuerelement mit der Semantik des gewählten Musters angegeben.
  • Slides außerhalb des sichtbaren Viewports dürfen keine fokussierbaren Elemente an visuell nicht erreichbaren Positionen enthalten. aria-hidden="true" wird nur verwendet, wenn Inhalte sowohl aus dem Accessibility Tree als auch aus der Fokusnavigation konsistent ausgeschlossen werden. Ein teilweise sichtbarer Slide gilt nicht als verborgen.
  • Navigationspunkte sind eine optionale Steuerung und kein erforderlicher Bestandteil jedes Sliders. Sie eignen sich, wenn die direkte Auswahl einer Position hilfreich ist und ihre Anzahl die Tab-Reihenfolge nicht übermäßig verlängert.
  • Jeder Punkt entspricht einer Scroll-Position (Snap-Punkt), nicht zwingend einem einzelnen Slide. Bewegt ein Schritt eine Slide-Gruppe, beschreibt der zugängliche Name des Punkts diese Gruppe. In Embla werden die Positionen mit scrollSnapList() ermittelt und über scrollTo(index) angesteuert.
  • Der aktive Punkt unterscheidet sich visuell und wird entsprechend dem gewählten Muster programmatisch gekennzeichnet. Bei einer Gruppe gewöhnlicher Auswahlbuttons wird der aktive Button nach dem APG-Muster mit aria-disabled="true" ausgezeichnet; eine Tabs-Implementierung erfordert das vollständige Tabs-Muster einschließlich aria-selected und der zugehörigen Tastatursteuerung. Farbe oder Transparenz allein reichen nicht aus.
  • Jeder Punkt-Button erhält einen zugänglichen Namen, der Zweck und Position beschreibt, zum Beispiel aria-label="Zu Position 2 von 5 wechseln". Bei gruppierten Slides nennt der Name den Bereich oder Inhalt der Gruppe.
  • Navigationspunkte werden als native <button>-Elemente mit sichtbarem :focus-visible umgesetzt. Ihr Pointer-Ziel muss WCAG 2.2 SC 2.5.8 entsprechen: mindestens 24 × 24 CSS-px oder eine anwendbare Ausnahme, etwa ausreichender Abstand oder ein gleichwertiges Steuerelement.
  • In Embla wird der Container der Navigationspunkte außerhalb des an useEmblaCarousel übergebenen Viewports platziert. Der Viewport verarbeitet Zeiger- und Drag-Eingaben, was Klicks auf darin platzierte Buttons beeinträchtigen kann.
  • Der Zustand der Punkte wird bei der Initialisierung sowie bei select und reInit mit Embla synchronisiert. Nach Änderungen der Containerbreite oder der Slide-Menge können sich die Anzahl der Snap-Punkte und der Navigationspunkte ändern.

Tastatur, Drag und automatisches Weiterschalten

  • Alle Aktionen, die mit Zeiger oder Touch verfügbar sind, sind auch mit der Tastatur bedienbar. Für die Navigation werden Buttons und die native Tab-Reihenfolge verwendet; die Aktivierung eines Pfeils oder Auswahlpunkts darf den Fokus nicht unerwartet verschieben.
  • Bei unterstütztem Drag mit dem Zeiger kann die Drag-Fläche im Ruhezustand den Cursor grab und während des Drückens und Ziehens grabbing anzeigen. Dieser visuelle Hinweis ersetzt weder Navigationsbuttons noch Tastatur- oder Touch-Bedienung.
  • Horizontales Wischen darf das vertikale Scrollen der Seite, die Textauswahl oder verschachtelte interaktive Elemente nicht beeinträchtigen.
  • Bei einem manuell bedienten Slider kann der Slide-Bereich aria-live="polite" verwenden, damit Änderungen ohne Unterbrechung der aktuellen Sprachausgabe angekündigt werden. Bei automatischer Rotation wird aria-live="off" verwendet, um fortlaufende Ansagen zu vermeiden.
  • Ein automatisch weiterschaltender Slider bietet eine zugängliche Schaltfläche zum Anhalten und Fortsetzen. Die Rotation stoppt, sobald ein Element des Sliders Tastaturfokus erhält, und solange sich der Zeiger über dem Slider befindet; ohne ausdrückliche Aktion wird sie nicht fortgesetzt.
  • Animationen berücksichtigen die Systemeinstellung prefers-reduced-motion: Bei angeforderter Bewegungsreduktion werden Übergänge verkürzt oder deaktiviert, sofern sie nicht zum Verständnis einer Zustandsänderung erforderlich sind.

Höhe und Performance

Höhe und Layoutstabilität

  • height: auto ist nicht grundsätzlich verboten. Die automatische Höhenbestimmung verursacht für sich genommen keinen Sprung; Layoutverschiebungen entstehen, wenn sich die Höhe des Sliders beim Inhaltswechsel unberücksichtigt ändert.
  • Wenn eine stabile Höhe für das Layout wichtig ist, wird der Platz über ein Seitenverhältnis, min-height oder eine einheitliche Slide-Höhe reserviert. Eine feste Höhe darf Inhalte bei längerem Text, einem Sprachwechsel oder beim Zoomen nicht abschneiden.
  • Bei Slides mit unterschiedlicher Höhe kann sich der Bereich an den aktiven Slide anpassen, sofern dies beabsichtigt ist und keine unerwarteten Verschiebungen des umgebenden Inhalts verursacht. Die Höhe wird mit realen Inhalten geprüft, einschließlich langer Überschriften und lokalisierter Fassungen.

Animation und Scrollen

  • Für Bewegungsanimationen werden transform und, wo passend, opacity bevorzugt, da Änderungen an left oder margin eine Neuberechnung des Layouts auslösen können. Die Auswahl wird durch Profiling überprüft; keine CSS-Eigenschaft garantiert allein flüssige Animationen.
  • will-change: transform wird nicht standardmäßig allen Slides hinzugefügt. Die Eigenschaft wird nur bei einem bestätigten Performance-Problem eingesetzt, da dauerhaft reservierte Ressourcen für viele Ebenen die Performance verschlechtern können.
  • Für Slider auf Basis nativen horizontalen Scrollens kann CSS Scroll Snap verwendet werden. Dadurch bleiben das erwartete Scrollverhalten des Browsers und die Interaktion mit Touch- sowie assistiven Technologien erhalten; das Verhalten wird in den Zielbrowsern geprüft.

Bildladen

  • Für Bilder, die beim initialen Laden sichtbar sind, einschließlich des kritischen ersten Slides, wird loading="lazy" nicht gesetzt. Lazy Loading eignet sich für Bilder, die anfangs außerhalb des Viewports liegen.
  • loading="lazy" legt keinen exakten Abstand von ein oder zwei Slides fest: Der Browser bestimmt den Ladebeginn anhand der Nähe zum Viewport und weiterer Bedingungen. Ein früheres Laden des nächsten Slides erfordert eine separate, durch Messungen und das Netzwerkbudget begründete Entscheidung.
  • Für Bilder werden width und height oder ein bekanntes Seitenverhältnis angegeben. Dadurch wird vor dem Laden Platz reserviert und das Risiko von Layoutverschiebungen reduziert.
  • Bei srcset und sizes müssen die Werte der responsiven Bildbreite innerhalb des Slides entsprechen. Vollformatige Bilder werden nicht ohne Not für Vorschaubilder geladen.

Prüfung

  • Zurück- und Weiter-Schaltflächen haben im endlichen und zyklischen Modus den korrekten Zustand; disabled ist für assistive Technologien verfügbar und visuell erkennbar.
  • Pfeile und Auswahlpunkte werden nur ausgeblendet, wenn keine Navigation erforderlich ist; der Überlauf wird nach der Initialisierung sowie nach Größenänderungen des Containers oder Inhalts geprüft.
  • Der Slider funktioniert mit Tastatur, Zeiger und Touch; der Fokus geht nicht verloren und wird nicht unerwartet verschoben.
  • Container, Slides, Pfeile und Navigationspunkte haben verständliche zugängliche Namen; die aktuelle Position wird mit der gewählten Semantik angegeben und die automatische Rotation kann angehalten werden.
  • Die Anzahl der Navigationspunkte entspricht den Snap-Positionen; ihr Zustand bleibt nach Drag, Positionsauswahl und Größenänderung synchron. Zielgröße oder Abstände werden anhand von WCAG 2.2 SC 2.5.8 geprüft.
  • aria-live im manuellen und automatischen Modus, die Einstellung zur Bewegungsreduktion und Slides außerhalb des sichtbaren Bereichs werden geprüft.
  • Auf mobilen Bildschirmgrößen werden die teilweise sichtbare Nachbarkarte, vertikales Seitenscrollen und fehlender horizontaler Seitenüberlauf überprüft.
  • Bilder reservieren vor dem Laden Platz; Lazy Loading verzögert den anfänglich sichtbaren kritischen Slide nicht.
  • Animationsfluss und Layoutverschiebungen werden auf Zielgeräten und in Zielbrowsern geprüft, nicht allein aus dem Vorhandensein von transform oder will-change abgeleitet.

Beispiele

Manuell bedienter Embla-Slider mit Navigation und Punkten:

'use client'

import { useCallback, useEffect, useState } from 'react'
import useEmblaCarousel from 'embla-carousel-react'
import type { EmblaCarouselType } from 'embla-carousel'

const slides = [
  { id: 'article-1', title: 'Erster Artikel', image: '/images/article-1.webp' },
  { id: 'article-2', title: 'Zweiter Artikel', image: '/images/article-2.webp' },
  { id: 'article-3', title: 'Dritter Artikel', image: '/images/article-3.webp' },
]

export const ArticleCarousel = () => {
  const [emblaRef, emblaApi] = useEmblaCarousel({ loop: false, slidesToScroll: 1 })
  const [scrollSnaps, setScrollSnaps] = useState<number[]>([])
  const [selectedSnap, setSelectedSnap] = useState(0)

  const onSelect = useCallback((api: EmblaCarouselType) => {
    setSelectedSnap(api.selectedScrollSnap())
  }, [])

  const onReInit = useCallback((api: EmblaCarouselType) => {
    setScrollSnaps(api.scrollSnapList())
    setSelectedSnap(api.selectedScrollSnap())
  }, [])

  useEffect(() => {
    if (!emblaApi) return

    onReInit(emblaApi)
    emblaApi.on('select', onSelect)
    emblaApi.on('reInit', onReInit)

    return () => {
      emblaApi.off('select', onSelect)
      emblaApi.off('reInit', onReInit)
    }
  }, [emblaApi, onReInit, onSelect])

  return (
    <section
      className='embla'
      role='region'
      aria-roledescription='Karussell'
      aria-labelledby='featured-title'
    >
      <h2 id='featured-title'>Empfohlene Artikel</h2>

      <div
        className='embla__viewport'
        ref={emblaRef}
        aria-live='polite'
        aria-atomic='false'
      >
        <div className='embla__container'>
          {slides.map((slide) => (
            <article
              className='embla__slide'
              key={slide.id}
              role='group'
              aria-roledescription='Folie'
              aria-labelledby={`slide-title-${slide.id}`}
            >
              <img
                src={slide.image}
                width='960'
                height='640'
                alt={`Titelbild: ${slide.title}`}
              />
              <h3 id={`slide-title-${slide.id}`}>{slide.title}</h3>
            </article>
          ))}
        </div>
      </div>

      {scrollSnaps.length > 1 && (
        <div className='embla__controls'>
          <button
            type='button'
            aria-label='Vorheriger Slide'
            disabled={!emblaApi?.canScrollPrev()}
            onClick={() => emblaApi?.scrollPrev()}
          >
            <span aria-hidden='true'>←</span>
          </button>
          <button
            type='button'
            aria-label='Nächster Slide'
            disabled={!emblaApi?.canScrollNext()}
            onClick={() => emblaApi?.scrollNext()}
          >
            <span aria-hidden='true'>→</span>
          </button>

          <div className='embla__dots' role='group' aria-label='Slider-Position auswählen'>
            {scrollSnaps.map((_, index) => {
              const isSelected = index === selectedSnap

              return (
                <button
                  className='embla__dot'
                  type='button'
                  key={index}
                  aria-label={`Zu Position ${index + 1} von ${scrollSnaps.length} wechseln`}
                  aria-disabled={isSelected}
                  onClick={() => {
                    if (!isSelected) emblaApi?.scrollTo(index)
                  }}
                >
                  <span aria-hidden='true' />
                </button>
              )
            })}
          </div>
        </div>
      )}
    </section>
  )
};

Das Beispiel verwendet manuelle Navigation; deshalb hat der Slide-Bereich aria-live="polite". Die Steuerelemente befinden sich außerhalb von .embla__viewport, damit Drag-Eingaben das Aktivieren der Buttons nicht beeinträchtigen. scrollSnapList() liefert die Navigationspunkte für jede Scroll-Position; ihr Zustand wird bei select und reInit aktualisiert. Das Attribut loading ist nicht angegeben, da die Anzahl der beim initialen Laden sichtbaren Bilder vom responsiven Layout abhängt. In einer produktiven Komponente werden anfangs sichtbare Bilder nicht verzögert geladen; Bilder außerhalb des initialen Viewports können lazy geladen werden. Bei pnpm muss embla-carousel zusätzlich als direkte Entwicklungsabhängigkeit eingetragen werden, wenn EmblaCarouselType importiert wird.

Responsives Embla-Layout mit teilweise sichtbarer nächster Karte und ausreichend großer Zielfläche für Navigationspunkte:

.embla__viewport {
  overflow: hidden;
  cursor: grab;
  touch-action: pan-y pinch-zoom;
}

.embla__viewport:active {
  cursor: grabbing;
}

.embla__container {
  display: flex;
  gap: 12px;
}

.embla__slide {
  flex: 0 0 calc(100% - 28px);
  min-width: 0;
}

.embla__dot {
  display: inline-grid;
  min-width: 24px;
  min-height: 24px;
  place-items: center;
  border: 0;
  background: transparent;
  cursor: pointer;
}

.embla__dot span {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: currentColor;
}

.embla__dot[aria-disabled='true'] span {
  width: 12px;
  height: 12px;
}

.embla__dot:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
}

@media (min-width: 768px) {
  .embla__slide {
    flex-basis: calc((100% - 24px) / 3);
  }
}

Bei einer Kartenbreite von calc(100% - 28px) und einem gap von 12px bleiben im Viewport etwa 16 px der nächsten Karte sichtbar. Dies ist ein Layoutbeispiel: Anzahl sichtbarer Karten, gap, Vorschau des Nachbar-Slides und Zielfläche der Navigationspunkte werden anhand der tatsächlichen Containerbreite geprüft. Bei Embla v8 werden API-Beispiele mit der Dokumentation dieser Major-Version abgeglichen.

Ähnliche Kategorien:

Ähnliche Artikel

  • Aufbau einer React-Komponente

    Meine Vorstellung davon, wie jede React-Komponente aussehen sollte

    422

    8 min.

  • SST - Einheitlicher Standard für Registerkarten

    Der SST-Standard legt Anforderungen an die Semantik, Barrierefreiheit und Funktionslogik von Registerkarten fest

    62

    6 min.

  • SSF-U – Einheitlicher Standard für Vollbildmodus

    Der SSF-U-Standard legt Anforderungen an die Semantik, Barrierefreiheit und Funktionslogik von Vollbildmodus fest

    248

    2 min.

  • SSA – Einheitlicher Standard für Akkordeons

    Der SSA-Standard legt Anforderungen an die Semantik, Barrierefreiheit und Funktionslogik von Akkordeons fest

    258

    2 min.

  • Bad Practices für Websites

    Analyse kritischer Fehler im Webdesign. Warum Slider, Autoplay und schwerfällige Seiten die Konversionsrate sowie die Platzierungen bei Google verschlechtern

    232

    2 min.

  • SSP - Einheitlicher Standard für Paginierung

    Der SSP-Standard legt Anforderungen an die Semantik, Barrierefreiheit und Funktionslogik von Paginierung fest

    240

    1 min.

  • Alle Artikel

Kontaktieren Sie mich