CSS-Only Carousel Navigation with scroll-marker

Carousel navigation without a JavaScript library

Carousels used to need libraries like Swiper.js or Slick for navigation buttons and dot indicators. CSS scroll-button and scroll-marker pseudo-elements give you native, accessible carousel UI.

Old way 15+ lines
/* JS: Swiper.js carousel with nav + dots */import Swiper from 'swiper';new Swiper('.carousel',   {  navigation:   {    nextEl: '.next',    prevEl: '.prev',  }  ,  pagination:   {    el: '.dots'  }  ,});/* + custom CSS for buttons, dots, states */
Modern
14 lines
.carousel::scroll-button(left)   {  content: "⬅" / "Scroll left";}.carousel::scroll-button(right)   {  content: "➡" / "Scroll right";}.carousel   {  scroll-marker-group: after;}.carousel li::scroll-marker   {  content: '';  width: 10px;  height: 10px;  border-radius: 50%;}
Limited availability 72% global usage

This feature is not Baseline because it does not work in some of the most widely-used browsers.

Not ready for production without a fallback.

135+
135+
scroll buttons and markers, no JS
::scroll-button() + ::scroll-marker()

Native performance

Scroll buttons and markers are browser-generated pseudo-elements. No JS, no DOM manipulation, no resize observers.

Accessible by default

Buttons are focusable and auto-disable at scroll ends. Markers work as anchor links. Keyboard and screen reader friendly.

Drop the library

Swiper.js is ~40 KB. The CSS approach is zero bytes of JavaScript and fully stylable.

Lines Saved
15+ → 14
Zero JS, native a11y
Old Approach
JS carousel lib
Swiper.js, Slick, Flickity
Modern Approach
CSS pseudo-elements
::scroll-button, ::scroll-marker

How it works

Building a carousel traditionally meant a JavaScript library: Swiper.js, Slick, or Flickity. These libraries create navigation buttons, dot indicators, handle scroll snap, and manage active states. That's a lot of JavaScript and custom CSS for what is fundamentally a scrolling UI.

CSS now provides two pseudo-elements for scroll containers. ::scroll-button(direction) creates prev/next buttons that scroll by ~85% of the container's visible area and auto-disable at the ends. ::scroll-marker on each item creates dot indicators grouped in a ::scroll-marker-group. Use the :target-current pseudo-class to style the active dot. Both are fully stylable with CSS.

Accessibility note: CSS-only carousels don't announce slide changes to screen readers. For production use, add role="region" and aria-label to the carousel container, and aria-label to each slide. Keyboard arrow navigation also requires JavaScript.
ESC