demo · v132

Carousel Navigation

The ::scroll-button() pseudo-element adds declarative prev/next buttons directly to a scroll container — no JavaScript needed. These two carousels use ::scroll-button(inline-start) and ::scroll-button(inline-end) for automatic ← → navigation, and the buttons disable themselves when the scroll reaches the edge.

Checking ::scroll-button() support…

Before and after Chrome 132

Before — JavaScript required
// HTML <div class="carousel"> <button id="prev">←</button> ... items ... <button id="next">→</button> </div> // JS — ~30 lines of boilerplate prevBtn.addEventListener('click', () => { carousel.scrollBy({ left: -300 }); }); nextBtn.addEventListener('click', () => { carousel.scrollBy({ left: 300 }); }); // + must manually disable buttons at edges // + must keep buttons accessible // + must handle resize events
Chrome 132 — pure CSS
// No extra HTML needed <div class="carousel"> ... items ... </div> /* CSS only — zero JS */ .carousel::scroll-button(inline-start), .carousel::scroll-button(inline-end) { display: flex; width: 2.5rem; height: 2.5rem; background: white; cursor: pointer; } .carousel::scroll-button(inline-start) { content: "←"; } .carousel::scroll-button(inline-end) { content: "→"; } /* Browser auto-disables at edges! */
/* The full ::scroll-button() direction set */
.scroller::scroll-button(inline-start) { content: "←"; } /* left  */
.scroller::scroll-button(inline-end)   { content: "→"; } /* right */
.scroller::scroll-button(block-start)  { content: "↑"; } /* up    */
.scroller::scroll-button(block-end)    { content: "↓"; } /* down  */

/* Auto-disable when at scroll edges */
.scroller::scroll-button(*):disabled {
  opacity: 0.3;
  cursor: not-allowed;
}

/* Style the active state */
.scroller::scroll-button(*):hover {
  background: var(--bg-stone);
}

see also