## CSS Carousels: Finally No JavaScript Required For years, carousels meant JavaScript libraries, complex accessibility considerations, and bundle size debates. In 2026, CSS can handle it natively with `::scroll-marker` and `::scroll-button` pseudo-elements. ## Browser Support (2026) | Browser | Support | Notes | |---------|---------|-------| | Chrome 135+ | Full | Since March 2025 | | Edge 135+ | Full | Chromium-based | | Firefox | Partial | Flag-enabled | | Safari 18.2+ | Full | Since Dec 2025 | **Global support: ~75%** (Growing rapidly) Consider progressive enhancement - scroll snap works everywhere, markers add enhanced UX. ## The Foundation: Scroll Snap Before markers, you need scroll snapping: ```css .carousel { display: flex; overflow-x: auto; scroll-snap-type: x mandatory; gap: 1rem; } .carousel-item { flex: 0 0 100%; scroll-snap-align: center; } ``` This gives you a basic swipeable carousel. Now let's add native navigation. ## Scroll Markers: Pagination Dots The `::scroll-marker` pseudo-element creates pagination indicators: ```css .carousel { scroll-marker-group: after; /* Show markers after carousel */ } .carousel-item::scroll-marker { content: ''; width: 12px; height: 12px; border-radius: 50%; background: rgba(0, 0, 0, 0.3); } .carousel-item::scroll-marker:target-current { background: #3b82f6; transform: scale(1.2); } ``` `:target-current` styles the marker for the currently visible item. ## Scroll Buttons: Previous/Next Navigation Native scroll buttons for arrow navigation: ```css .carousel::scroll-button(prev) { content: '←'; position: absolute; left: 0; /* Styles for previous button */ } .carousel::scroll-button(next) { content: '→'; position: absolute; right: 0; /* Styles for next button */ } /* Hide when can't scroll further */ .carousel::scroll-button(prev):disabled, .carousel::scroll-button(next):disabled { opacity: 0.3; pointer-events: none; } ``` ## Complete Carousel Example ```css .carousel-wrapper { position: relative; } .carousel { display: flex; overflow-x: auto; scroll-snap-type: x mandatory; scroll-behavior: smooth; gap: 1rem; padding: 1rem; /* Enable scroll markers */ scroll-marker-group: after; /* Hide scrollbar */ scrollbar-width: none; &::-webkit-scrollbar { display: none; } } .carousel-item { flex: 0 0 calc(100% - 2rem); scroll-snap-align: center; border-radius: 8px; background: #f1f5f9; padding: 2rem; } /* Pagination dots */ .carousel-item::scroll-marker { content: ''; display: inline-block; width: 10px; height: 10px; margin: 0 4px; border-radius: 50%; background: #cbd5e1; cursor: pointer; transition: all 0.2s ease; } .carousel-item::scroll-marker:target-current { background: #3b82f6; transform: scale(1.3); } .carousel-item::scroll-marker:hover:not(:target-current) { background: #94a3b8; } /* Navigation buttons */ .carousel::scroll-button(prev), .carousel::scroll-button(next) { position: absolute; top: 50%; transform: translateY(-50%); width: 40px; height: 40px; border-radius: 50%; background: white; box-shadow: 0 2px 8px rgba(0,0,0,0.15); display: flex; align-items: center; justify-content: center; cursor: pointer; transition: opacity 0.2s; } .carousel::scroll-button(prev) { content: '‹'; left: 8px; } .carousel::scroll-button(next) { content: '›'; right: 8px; } .carousel::scroll-button(prev):disabled, .carousel::scroll-button(next):disabled { opacity: 0; pointer-events: none; } ``` ## Multi-Item Carousel Show multiple items at once: ```css .multi-carousel { display: flex; overflow-x: auto; scroll-snap-type: x mandatory; scroll-marker-group: after; } .multi-carousel-item { flex: 0 0 calc(33.333% - 1rem); /* 3 items visible */ scroll-snap-align: start; } @media (max-width: 768px) { .multi-carousel-item { flex: 0 0 calc(50% - 0.5rem); /* 2 items on tablet */ } } @media (max-width: 480px) { .multi-carousel-item { flex: 0 0 100%; /* 1 item on mobile */ } } ``` ## Vertical Carousel Works on Y-axis too: ```css .vertical-carousel { display: flex; flex-direction: column; height: 400px; overflow-y: auto; scroll-snap-type: y mandatory; scroll-marker-group: inline-end; /* Markers on the right */ } .vertical-carousel-item { flex: 0 0 100%; scroll-snap-align: start; } .vertical-carousel-item::scroll-marker { content: ''; display: block; width: 10px; height: 10px; margin: 4px 0; border-radius: 50%; background: #cbd5e1; } ``` ## Progressive Enhancement For browsers without scroll marker support: ```css /* Base experience - always works */ .carousel { display: flex; overflow-x: auto; scroll-snap-type: x mandatory; } /* Enhanced experience when supported */ @supports (scroll-marker-group: after) { .carousel { scroll-marker-group: after; } .carousel-item::scroll-marker { content: ''; /* ... marker styles */ } /* Hide JS fallback dots */ .js-pagination { display: none; } } ``` ## Accessibility Considerations CSS carousels get accessibility improvements for free: 1. **Keyboard navigation** - Arrow keys scroll between items 2. **Focus management** - Markers are focusable by default 3. **Screen readers** - Native semantics for navigation Enhance with ARIA where needed: ```html