# Embla Carousel React: barrierefreie Slides ohne Layout Shift

> Bau mit Embla Carousel React ein Karussell für Fotos, Inserate, Testimonials und Onboarding, mit Tastatursteuerung, pausierbarem Autoplay und ohne Layout Shift.

- Autor: [Serban Rusu](https://wingo-ui.com/blog/authors/serban), Founder of Wingo UI
- Veröffentlicht: 19. Okt. 2026
- Kategorie: [Komponenten-Guides](https://wingo-ui.com/de/blog/category/components)
- Lesezeit: 9 Min.
- Kanonische URL: https://wingo-ui.com/de/blog/embla-carousel-react
- Englische Fassung: https://wingo-ui.com/blog/embla-carousel-react

## Kurz gesagt

Embla gibt React einen Hook, useEmblaCarousel, der Drag-Physik und Einrasten übernimmt; das Markup, die Slide-Breiten in CSS und alle Bedienelemente lieferst du selbst. Ein Karussell für die Produktion braucht dazu Pfeile, Dots, Tastaturbedienung, inerte Slides außerhalb des Sichtbereichs, einen Pause-Button für Autoplay und Slide-Breiten aus dem Server-HTML, damit beim Laden nichts springt. Das Carousel von Wingo UI packt all das als Props um Embla, und das Onboarding Carousel baut darauf Slides für den ersten App-Start auf.

Du brauchst eine Fotogalerie, eine Leiste mit Produktkarten, Testimonials oder Willkommens-Slides, und du hast Embla gewählt, weil es sich auf dem Smartphone gut ziehen lässt. Der Hook von Embla Carousel React liefert dir Drag-Physik und Einrasten mit ein paar Zeilen Markup. Danach fängt die eigentliche Arbeit an: Pfeile, Dots, Tastaturbedienung, Autoplay, das man stoppen kann, Slides, die bei der Hydration nicht springen, und Screenreader, die „3 of 8“ hören statt eines Haufens Bilder. Dieser Guide zeigt, was Embla dir gibt, was du noch selbst bauen musst und wie das [Carousel](https://wingo-ui.com/components/carousel) von Wingo UI jeden dieser Punkte löst.

## Was macht Embla Carousel in React?

Es verschiebt den Track, um alles andere kümmerst du dich. Embla ist eine [Karussell-Engine ohne Abhängigkeiten](https://github.com/davidjerleke/embla-carousel) unter MIT-Lizenz. Dieser Guide nutzt Embla 8, die stabile Versionsreihe (8.6.0, Stand Oktober 2026, [Doku zu v8](https://www.embla-carousel.com/docs/v8/api/methods)). Das React-Paket stellt einen einzigen Hook bereit, `useEmblaCarousel`, der eine Ref für den Viewport und eine API für `scrollNext`, `scrollTo`, `selectedScrollSnap` und Events zurückgibt. Das Markup besteht aus drei verschachtelten Elementen, und die Slide-Breiten legst du selbst in CSS fest. So sieht ein minimales React Carousel mit Tailwind aus:

```tsx
"use client";

import useEmblaCarousel from "embla-carousel-react";

export function BareCarousel({ photos }: { photos: string[] }) {
  const [emblaRef] = useEmblaCarousel({ loop: false });

  return (
    <div ref={emblaRef} className="overflow-hidden">
      <div className="flex">
        {photos.map((src) => (
          <div key={src} className="min-w-0 shrink-0 grow-0 basis-full">
            <img src={src} alt="" draggable={false} className="aspect-[4/3] w-full object-cover" />
          </div>
        ))}
      </div>
    </div>
  );
}
```

Das lässt sich ziehen und rastet ein. Pfeile, Dots, Tastaturbedienung oder Namen für die Slides gibt es nicht, und um den aktuellen Slide anzuzeigen, abonnierst du das Event `select` und liest `selectedScrollSnap()`. Embla lässt das absichtlich weg, um klein zu bleiben, und deshalb bestehen die meisten Embla Carousel Beispiele, die du findest, vor allem aus Code für die Bedienelemente. Ein Hinweis zur Version: Die Embla-Doku startet inzwischen mit dem Release Candidate von 9.0, der diese Methoden umbenennt (`goToNext`, `goTo`, `selectedSnap`). Prüf also, für welche Version ein Snippet geschrieben ist, bevor du es kopierst.

## Wie unterscheiden sich die Karussells von Embla, shadcn und Wingo UI?

Alle drei nutzen dieselbe Engine, Ziehen und Einrasten fühlen sich also gleich an; der Unterschied liegt darin, wie viel von der Bedienschicht mitkommt. Die Fakten stammen aus der öffentlichen Doku der Projekte, Stand Oktober 2026.

| Option | Was du bekommst | Slide-Breiten | Dots, Thumbnails | Autoplay |
|---|---|---|---|---|
| [embla-carousel-react](https://www.embla-carousel.com/docs/v8/api/methods) | Den Hook und die API | Dein CSS | Baust du aus der API | Plugin [embla-carousel-autoplay](https://www.embla-carousel.com/docs/v8/plugins/autoplay), kein Pause-Button |
| [shadcn/ui Carousel](https://ui.shadcn.com/docs/components/carousel) | Kostenlose Quellcode-Teile: `Carousel`, `CarouselContent`, `CarouselItem`, `CarouselPrevious`, `CarouselNext` | `basis-*`-Klassen an jedem Item | Nicht in der Doku; das API-Beispiel zeigt einen Zähler „Slide X of Y“ | Das Embla-Plugin über die Prop `plugins` |
| [Wingo UI Carousel](https://wingo-ui.com/components/carousel) | Pro-Komponente als Quellcode, basiert auf Embla 8 | `slidesPerView`, je nach Containerbreite | `indicator` (Dots, Balken, Fortschritt, Zähler) und `thumbnails` | Eingebaut, mit einem Pause-Button, der immer sichtbar ist |

Das shadcn Carousel ist die bessere Wahl, wenn du kostenlose, minimale Teile willst und Dots und Autoplay-Steuerung ohnehin selbst schreiben willst. Reines Embla passt, wenn das Design so ungewöhnlich ist, dass dir jeder Wrapper im Weg steht. Der Rest dieses Guides behandelt die Version von Wingo UI.

## Welche Props braucht ein Karussell in React?

Meist keine außer den Slides. Ohne Props zeigt das Carousel jeweils einen Slide mit Dots darunter, Pfeile bei Maus oder Trackpad und auf dem Smartphone den Anschnitt des nächsten Slides am Rand. Installier es und übergib die Slides als Children:

```bash
npx wingo-ui@latest add carousel
```

```tsx
import { Carousel } from "@/components/ui/carousel";

const photos = ["/photos/living-room.jpg", "/photos/kitchen.jpg", "/photos/bedroom.jpg"];

export function ListingPhotos() {
  return (
    <Carousel label="Fotos der Wohnung" indicator="counter" indicatorPosition="overlay" thumbnails={photos}>
      {photos.map((src, i) => (
        <img key={src} src={src} alt={`Foto ${i + 1}`} draggable={false} className="aspect-[4/3] w-full object-cover" />
      ))}
    </Carousel>
  );
}
```

Jedes Child wird automatisch in einen Slide gepackt. Setz `draggable={false}` bei Bildern, damit das browsereigene Ziehen von Bildern nicht mit dem Wischen kollidiert. Als Nächstes brauchst du meist `slidesPerView`, `gap` (`none`, `sm`, `md`, `lg`), `align` (`start` oder `center`), `loop`, `arrows` (`auto`, `always`, `never`), `arrowPosition` (`inside`, `outside`, `header`) und `indicator`. Alles, was Embla unterstützt und keine eigene Prop hat, etwa `slidesToScroll` oder `duration`, geht über `opts`, und `setApi` gibt dir die Embla-API.

Zieh die Fotos, klick dann ins Karussell und nutz die Pfeiltasten links und rechts. Auf dem Smartphone schaut das nächste Inserat am Rand hervor.

> Live-Demo (Carousel): Wisch oder zieh durch die Inserate, klick auf einen Dot, um zu springen, oder fokussier das Karussell und drück Pfeil links, Pfeil rechts, Pos1 und Ende. Probier sie unter [Carousel](https://wingo-ui.com/components/carousel) aus und installier sie mit `npx wingo-ui@latest add carousel`.

## Wie baue ich Inserat-Leisten und Testimonials?

Ändere `slidesPerView` und zwei oder drei Layout-Props. Eine Zahl zeigt so viele Slides; eine Kommazahl lässt den nächsten am Rand hervorschauen (1.15 zeigt 15% des nächsten). Ein Objekt legt den Wert je nach Breite des Karussells fest, sodass eine Leiste in einer schmalen Sidebar weniger Karten zeigt als dieselbe Leiste über die ganze Seite:

```tsx
import { Carousel } from "@/components/ui/carousel";
import { Card } from "@/components/ui/card";

type Listing = { id: string; photo: string; title: string; city: string; price: string };

export function RecommendedRail({ listings }: { listings: Listing[] }) {
  return (
    <Carousel
      title="Empfohlen für dich"
      label="Empfohlene Inserate"
      slidesPerView={{ base: 1.15, md: 2.5, lg: 3.5 }}
      arrowPosition="header"
      indicator="none"
    >
      {listings.map((item) => (
        <Card key={item.id} variant="outline" size="sm" className="h-full" media={item.photo} title={item.title} description={`${item.city} · ${item.price}`} />
      ))}
    </Carousel>
  );
}
```

Die Breakpoints sind Container Queries auf dem Karussell selbst (`sm` ab 640px, `md` ab 768px, `lg` ab 1024px). Für Testimonials zentrierst du den ausgewählten Slide und lässt die anderen schrumpfen:

```tsx
<Carousel label="Kundenbewertungen" effect="scale" align="center" loop slidesPerView={{ base: 1.2, md: 1.8 }} peek={false} radius="lg">
  {reviews.map((review) => (
    <figure key={review.name} className="flex h-full flex-col gap-4 bg-card p-5">
      <blockquote className="flex-1 text-[15px] leading-relaxed">{review.quote}</blockquote>
      <figcaption className="text-sm font-medium">{review.name}</figcaption>
    </figure>
  ))}
</Carousel>
```

`effect="scale"` verkleinert Slides abseits des Einrastpunkts auf 0.9 und dunkelt sie ab. Statt React-State liest der Effekt eine CSS-Variable, die bei jedem Scroll-Event geschrieben wird, sodass ein Ziehen die Slides nicht neu rendert. Für eine Leiste mit Chips, die ohne Einrasten gleiten soll, nimm `slidesPerView="auto"` mit `dragFree`.

## Wie verhindere ich Layout Shift beim Laden eines Karussells?

Schreib die Slide-Breiten in CSS, das der Server rendert. Embla [misst die berechnete Größe deiner Slides](https://www.embla-carousel.com/docs/v8/guides/slide-sizes) und setzt selbst nie Breiten. Der klassische Layout Shift entsteht also durch Code, der in einem Effect das Fenster misst und danach die Slides dimensioniert. Beim ersten Paint ist jeder Slide noch voll breit, dann springen alle auf ein Drittel.

Das Carousel macht aus `slidesPerView` CSS Custom Properties pro Container-Breakpoint, sodass das Server-HTML schon die endgültigen Breiten und den Anschnitt des nächsten Slides auf dem Smartphone enthält. Die Zahl der Dots wird aus der Zahl der Slides geschätzt, bevor Embla gemessen hat, also erscheint auch der Indikator nicht verspätet. Zwei Dinge bleiben bei dir:

1. **Gib Medien eine feste Form.** Nutz eine `aspect-*`-Klasse oder die Attribute width und height an jedem Bild. Ein Bild ohne Maße ändert beim Laden die Höhe des Slides, und Embla muss neu messen.
2. **Lade die ersten Slides sofort.** Markier das erste oder die ersten zwei Bilder mit `loading="eager"` und den Rest mit `lazy`.

## Wie mache ich ein Embla-Karussell barrierefrei?

Folg dem [Carousel-Pattern des W3C](https://www.w3.org/WAI/ARIA/apg/patterns/carousel/): eine beschriftete Region mit `aria-roledescription="carousel"`, Slides als benannte Gruppen, Buttons für Zurück und Weiter und eine Live-Region, die still bleibt, solange Autoplay läuft. Das Carousel rendert genau diese Struktur. Jeder Slide bekommt die Rolle group und einen Namen wie „3 of 8“, und der Slide-Container ist `aria-live="polite"`, solange Autoplay pausiert, und `off`, während es läuft.

Die Tastaturbedienung geht über das Minimum des Patterns hinaus. Der Viewport selbst ist fokussierbar: Pfeil links und rechts blättern um einen Slide weiter (Pfeil hoch und runter bei `orientation="vertical"`), Pos1 und Ende springen an den Anfang und ans Ende. Eingabefelder in Slides behalten ihre eigenen Pfeiltasten. Dots und Thumbnails sind jeweils ein einziger Tab-Stopp, bei dem der Fokus der Auswahl folgt, und Slides außerhalb des Sichtbereichs sind `inert`, sodass Tab nie auf einem Link landet, den du nicht siehst. Wird ein Pfeil am Ende des Tracks deaktiviert, wandert der Fokus zum anderen Pfeil, statt auf der Seite verloren zu gehen.

Benenn das Karussell mit `label` nach seinem Inhalt. Der Standard ist „Carousel“, was ein Screenreader als „Carousel, carousel“ vorliest, und das W3C-Pattern rät vom Wort Carousel im Label ab. Jeder Text, auch die Namen „3 of 8“ und „Go to slide 3“, steckt in `labels` und wird mit `DEFAULT_CAROUSEL_LABELS` zusammengeführt. Zum Übersetzen musst du also nichts forken.

## Wie baue ich Autoplay, das man stoppen kann?

Setz `autoplay` und sorg für einen Pause-Button: Das Carousel rendert ihn immer, solange Autoplay an ist. Das WCAG-2.2-Erfolgskriterium [2.2.2 Pause, Stop, Hide](https://www.w3.org/WAI/WCAG22/Understanding/pause-stop-hide.html) (Stufe A) verlangt einen Weg, Bewegung zu pausieren, die von selbst startet und länger als fünf Sekunden dauert. Das offizielle Plugin für Embla Carousel Autoplay stoppt standardmäßig nach einem Ziehen (`stopOnInteraction: true`) und stellt `play()` und `stop()` bereit; das sichtbare Bedienelement baust du selbst.

```tsx
<Carousel label="Angebote" autoplay loop interval={4500} indicator="bars" indicatorPosition="overlay" peek={false} radius="lg">
  {offers.map((offer, i) => (
    <div key={offer.title} className="relative aspect-[4/5] w-full @min-[640px]:aspect-[16/8]">
      <img src={offer.photo} alt="" draggable={false} loading={i === 0 ? "eager" : "lazy"} className="absolute inset-0 size-full object-cover" />
      <p className="absolute inset-x-5 bottom-14 text-[22px] font-bold text-white">{offer.title}</p>
    </div>
  ))}
</Carousel>
```

Den Timer bringt die Komponente selbst mit, ein Plugin braucht es nicht. Er pausiert, solange eine Maus über dem Karussell liegt (`pauseOnHover`), solange der Tastaturfokus darin ist, während des Ziehens, wenn der Tab im Hintergrund ist und wenn das Karussell aus dem sichtbaren Bereich gescrollt ist. Ohne `loop` stoppt er auf dem letzten Slide. `stopOnInteraction` entscheidet, ob ein Ziehen oder ein Pfeil den Timer neu startet (der Standard) oder Autoplay anhält, bis jemand auf Play drückt. Ist reduzierte Bewegung eingestellt, startet Autoplay pausiert, und Pfeile springen, statt zu gleiten; der [Guide zu prefers-reduced-motion](https://wingo-ui.com/de/blog/prefers-reduced-motion-react) erklärt, wie das ohne Hydration Mismatch gelesen wird.

Schalt unten Autoplay ein und sieh zu, wie sich die Story-Balken füllen. Fahr dann mit der Maus über das Karussell oder spring mit Tab hinein, um es zu pausieren.

> Live-Demo (Carousel): Die Story-Balken füllen sich, während Autoplay läuft. Fahr mit der Maus darüber oder fokussier das Karussell zum Pausieren, oder drück den Pause-Button zum Stoppen. Probier sie unter [Carousel](https://wingo-ui.com/components/carousel) aus und installier sie mit `npx wingo-ui@latest add carousel`.

## Wie verhält sich das Karussell auf dem Smartphone?

Es lässt sich wischen und kommt dem vertikalen Scrollen nicht in die Quere. Unter 640px Karussellbreite zeigt ein ganzzahliges `slidesPerView` 12% des nächsten Slides, damit man sieht, dass noch mehr kommt (`peek={false}` schaltet das ab). Mit `arrows="auto"` verschwinden die Pfeile auf Touch-Geräten, Dots bekommen 44px große Tippflächen, und der Track setzt `touch-action: pan-y`, damit ein vertikales Wischen weiterhin die Seite scrollt. Auf dem Laptop bewegt ein seitliches Wischen mit zwei Fingern auf dem Trackpad genau einen Slide pro Wischen und löst nicht die Zurück-Geste des Browsers aus; setz `wheel={false}`, wenn das Karussell in einem Container steckt, der seitlich scrollt.

## Wie baue ich Onboarding-Slides mit Embla?

Nimm das [Onboarding Carousel](https://wingo-ui.com/components/onboarding-carousel), das auf dem Carousel aufbaut. Es ergänzt Skip, ein Next, das auf dem letzten Slide zu Get started wird, eine Hintergrundfarbe pro Slide, die beim Wischen überblendet, und `persistKey`, das eine abgeschlossene Tour im localStorage speichert, damit sie nicht noch einmal erscheint.

```bash
npx wingo-ui@latest add onboarding-carousel
```

```tsx
"use client";

import { useRouter } from "next/navigation";
import { OnboardingCarousel, type OnboardingSlide } from "@/components/ui/onboarding-carousel";

const slides: OnboardingSlide[] = [
  { id: "clients", title: "Alle Kunden an einem Ort", description: "Firmen, Kontakte und Salden auf deinem Smartphone.", image: "/onboarding/clients.svg", background: "var(--info-soft)" },
  { id: "invoices", title: "Rechnungen in 30 Sekunden", description: "Stell eine Rechnung aus, und wir mailen sie an deinen Kunden.", image: "/onboarding/invoices.svg", background: "var(--success-soft)" },
  { id: "offline", title: "Funktioniert auch offline", description: "Alles wird synchronisiert, sobald du wieder online bist.", image: "/onboarding/offline.svg" },
];

export default function Welcome() {
  const router = useRouter();
  return <OnboardingCarousel layout="screen" slides={slides} persistKey="onboarding-v1" showBack onComplete={() => router.replace("/home")} />;
}
```

`layout="screen"` füllt den Viewport mit Safe-Area-Abstand, `card` erzeugt eine Fläche von 420 mal 640px, und die Prop `modal` öffnet es in einem Dialog, der auf dem Smartphone den ganzen Bildschirm füllt und ab 768px eine zentrierte Karte ist. Jeder Slide wird angesagt, sobald er eingerastet ist, und Escape im Modal zählt als Skip.

> Live-Demo (Onboarding Carousel): Wisch durch die Slides oder drück Continue. Skip springt zum letzten Slide, und Get started beendet die Tour. Probier sie unter [Onboarding Carousel](https://wingo-ui.com/components/onboarding-carousel) aus und installier sie mit `npx wingo-ui@latest add onboarding-carousel`.

Für einen eigenen Footer in einem einfachen Karussell nimmst du die zusammensetzbaren Teile (`CarouselRoot`, `CarouselViewport`, `CarouselSlide`, `CarouselDots`) und den Hook `useCarousel`, die dir `selectedIndex`, `snapCount`, `scrollNext` und `scrollTo` liefern. Damit ist ein Continue-Button in ein paar Zeilen gebaut. Das [Tutorial zum React-Onboarding-Flow](https://wingo-ui.com/de/blog/react-onboarding-flow) kombiniert diese Slides mit Berechtigungsabfragen und einem Install-Prompt.

## Was sind die typischen Fallstricke bei Embla-Karussells?

- **Bilder, die sich ziehen lassen.** Ohne `draggable={false}` packt ein Ziehen mit der Maus auf einem Foto das Bild selbst, statt den Track zu bewegen.
- **Links in verborgenen Slides.** Tab läuft in Slides außerhalb des Sichtbereichs, solange sie nicht inert sind, und wer die Tastatur nutzt, landet auf einem Link, den er nicht sieht.
- **Autoplay ohne Pause-Button.** Bewegung, die ohne einen solchen Button länger als fünf Sekunden läuft, verstößt gegen WCAG 2.2.2, und sie schiebt den Slide weg, während jemand ihn erst halb gelesen hat.
- **Ein Karussell für Inhalte, die jeder sehen muss.** Alles auf Slide vier bleibt verborgen, bis jemand dreimal wischt. Pack das, worauf es ankommt, in eine statische Karte und nutz Karussells zum Stöbern, wie in der [Marketplace-Vorlage](https://wingo-ui.com/de/blog/react-marketplace-template).

## Wie füge ich es meinem Projekt hinzu?

Führ `npx wingo-ui@latest add carousel` aus (oder `onboarding-carousel`). Die CLI kopiert den Quellcode nach `components/ui`, ergänzt den [Button](https://wingo-ui.com/components/button) sowie die gemeinsamen Hooks und Helfer, die die Komponente importiert, und installiert `embla-carousel-react`, `motion` und `lucide-react`. Beide Karussells sind Pro-Komponenten ([Preise](https://wingo-ui.com/de/pricing)). Weitere Deep Dives zu einzelnen Komponenten findest du in der [Kategorie Komponenten-Guides](https://wingo-ui.com/de/blog/category/components), und der [Guide zu React-Formularkomponenten](https://wingo-ui.com/de/blog/react-form-components-guide) wendet dieselben Regeln für Tastatur, Touch und Screenreader auf jedes Formularelement an.

## Komponenten in diesem Artikel

- [Carousel](https://wingo-ui.com/components/carousel) (Pro): A swipeable React carousel on Embla for photos, cards, testimonials and onboarding, with peek, arrows, dots, thumbnails and autoplay. Installation: `npx wingo-ui@latest add carousel`
- [Onboarding Carousel](https://wingo-ui.com/components/onboarding-carousel) (Pro): A React onboarding carousel of first-run slides for an app or PWA: swipe or tap Next, follow the dots, Skip any time, then Get started. Installation: `npx wingo-ui@latest add onboarding-carousel`

## Häufige Fragen

### Was ist Embla Carousel in React?

Embla Carousel ist eine Karussell-Engine unter MIT-Lizenz ohne Abhängigkeiten. Das Paket embla-carousel-react stellt den Hook useEmblaCarousel bereit, der eine Ref für den Viewport und eine API für Scrollen, Events und Zustand zurückgibt; Markup, CSS und Bedienelemente schreibst du selbst.

### Wie füge ich einem Embla-Karussell Autoplay hinzu?

Mit reinem Embla installierst du das offizielle Plugin embla-carousel-autoplay und übergibst es an useEmblaCarousel; in Version 8 liegt die dokumentierte Standardverzögerung bei 4000 ms, und es stoppt, sobald jemand zieht (stopOnInteraction ist standardmäßig true). Einen Pause-Button musst du trotzdem selbst rendern, denn WCAG 2.2.2 verlangt einen Weg, Bewegung zu stoppen, die länger als fünf Sekunden läuft.

### Basiert das shadcn Carousel auf Embla?

Ja. Das Carousel von shadcn/ui legt Carousel, CarouselContent, CarouselItem, CarouselPrevious und CarouselNext um Embla, setzt Slide-Breiten über basis-Klassen und nimmt Embla-Optionen und Plugins als Props. Dots, Thumbnails oder einen Pause-Button für Autoplay liefert die Doku nicht mit, die baust du aus der API.

### Wie verhindere ich, dass ein Embla-Karussell beim Laden das Layout verschiebt?

Leg die Slide-Breiten in CSS fest, damit das Server-HTML schon die richtige Größe hat, und gib jedem Bild ein festes Seitenverhältnis. Embla misst die Slides nach dem Mount und setzt selbst nie Breiten, also springt alles, was nach der Hydration per JavaScript dimensioniert wird.

### Ist das Carousel von Wingo UI kostenlos?

Nein. Carousel und Onboarding Carousel sind Pro-Komponenten. Button und Card aus den Beispielen sind kostenlos.

---

Quelle: https://wingo-ui.com/de/blog/embla-carousel-react
