Zum Hauptinhalt springen
4 Min. LesezeitVon Klemens Kaindl

Swiper.js kann weg, Carousels gehen jetzt in reinem CSS

Chrome 135 hat ::scroll-marker und ::scroll-button stabil gemacht. Damit baut man komplette Carousels ohne eine Zeile JavaScript.

Jedes zweite Kundenprojekt der letzten fünf Jahre hatte irgendwo ein Carousel. Hero-Slider, Produktreihe, Logo-Wand, Referenz-Kacheln. Und jedes Mal kam Swiper.js dazu. 140 KB JavaScript für drei Folien, die nach links rutschen. Plus die Touch-Bibliothek, plus die A11y-Patches, plus die Bug-Reports vom iOS-Safari, der die Snap-Punkte falsch interpretiert.

Das ist seit Chrome 135 vorbei. Native CSS Carousels sind stabil. Inkl. Pagination-Dots, Vor- und Zurück-Buttons, Tastatur-Navigation und Scroll-Snap. Komplett ohne JavaScript.

Die drei Bausteine

Das Ganze hängt an drei Bausteinen aus CSS Overflow Level 5 und Scroll-Driven Animations.

scroll-snap-type kennst du wahrscheinlich schon. Das ist die Mechanik, die den Scroll-Container auf einer Folie einrasten lässt. Gibt es seit Jahren.

Neu sind zwei Pseudo-Elemente: ::scroll-marker und ::scroll-button. Der Marker ist der Pagination-Dot pro Slide. Der Button ist der Pfeil zum Weiterklicken. Beide werden vom Browser generiert, sind nativ tastaturbedienbar und screenreader-tauglich. Du musst nichts dazustellen, kein aria-label setzen, kein Focus-Management bauen.

Minimal-Beispiel

<ul class="carousel">
  <li>Folie 1</li>
  <li>Folie 2</li>
  <li>Folie 3</li>
</ul>
.carousel {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-marker-group: after;
}

.carousel li {
  flex: 0 0 100%;
  scroll-snap-align: center;
}

.carousel li::scroll-marker {
  content: "";
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: #444;
}

.carousel li::scroll-marker:target-current {
  background: #EEFF00;
}

Das war's. Punkte unter dem Carousel, der aktive ist gelb, Klick auf einen Punkt scrollt zur passenden Folie, Pfeil-Tasten funktionieren, Swipe auf Touch klappt sowieso, weil es ja ein nativer Scroll-Container ist.

Für die Pfeil-Buttons kommt ::scroll-button dazu:

.carousel::scroll-button(left) { content: "←"; }
.carousel::scroll-button(right) { content: "→"; }

Der Browser deaktiviert die Buttons automatisch am Anfang und Ende. Kein State-Tracking, kein disabled-Attribut von Hand.

Was den Unterschied macht

Drei Sachen, die Swiper-Setups jahrelang verkompliziert haben, sind plötzlich umsonst.

Accessibility. Marker und Buttons sind echte UI-Elemente im Accessibility-Tree. Screenreader sagen "Liste mit 3 Elementen, Element 2 aktuell ausgewählt". Bei Swiper musste man dafür aria-roledescription, aria-live und Focus-Trapping selber stricken. Habe ich nie hundertprozentig hingekriegt.

Reduced Motion. Wenn der User in den Systemeinstellungen Bewegungsreduktion aktiviert hat, scrollt der Browser ohne Smooth-Behavior. Auch das umsonst. Bei JS-Carousels muss man prefers-reduced-motion in der Animation-Config abfragen, und die meisten Plugins tun das nicht.

Performance. Kein Bundle, kein Render-Blocking, keine Hydration. Eine Hero-Carousel mit Bildern wird zum HTML-First-Paint. Bei Swiper wartet das Layout, bis das JS geladen ist, sonst sieht man ein vertikal gestapeltes Listengrauen.

Wo es noch hakt

Safari hängt noch hinterher, Firefox auch. Stand Mai 2026: Chrome und alle Chromium-Browser laufen, Safari hat es in der Tech Preview, Firefox arbeitet dran. Heißt für die Praxis: rund 75 Prozent der User sehen das volle Carousel mit Dots und Pfeilen. Der Rest sieht einen scrollbaren Container mit Snap-Punkten. Das ist immer noch eine funktionierende UI, nur ohne Pagination-Indikator.

Für Kundenwebsites reicht mir das. Wer Safari benutzt und unbedingt einen Dot sehen muss, kann eine Folie weiter swipen. Niemand fühlt sich ausgesperrt. Progressive Enhancement, wie es gedacht war.

Wenn du nicht warten willst, kannst du als Fallback ein paar statische Dots dazustellen, die nur sichtbar werden, wenn @supports not (selector(::scroll-marker)) greift. Ein paar Zeilen CSS, kein JS.

Was das praktisch heißt

Ich habe letzte Woche das Carousel auf einer Kundenwebsite ausgetauscht. Vorher: Swiper.js plus React-Wrapper plus eigenes Theming, zusammen 38 KB gezippt. Nachher: 18 Zeilen CSS, null KB JavaScript. LCP ist von 1.4 s auf 0.9 s gerutscht, einfach weil ein Render-Blocker weg ist.

Das gleiche Pattern wie bei View Transitions, Anchor Positioning oder :has(). Sachen, für die wir noch vor zwei Jahren eine Library installiert haben, sind jetzt Plattform. Weniger npm install, weniger Wartung, weniger Bugs an der Schnittstelle zwischen Framework und Browser-Quirk.

Für Solo-Freelancer wie mich ist das pures Gold. Eine Abhängigkeit weniger pro Projekt heißt einen Bug weniger in drei Jahren, wenn der Kunde anruft, weil "irgendwas nicht mehr geht".

Sources:

Weiterlesen