Others
Carousel
AvailableAutomatically rotating slides. Play/pause controls and screen reader considerations are essential.
What Is a Carousel?
A carousel is a UI component that cycles through multiple slides (banners, features, photos, etc.) in the same area. It's commonly used for hero banners on homepages and product galleries.
While convenient, carousels are prone to accessibility issues when theyauto-rotate without user control, cannot be stopped, orcannot be operated with a keyboard — making them one of the most problematic patterns.
Why Does Accessibility Matter?
- People who struggle with auto-playing content. Continuously moving banners are a significant burden for people who read slowly, have difficulty maintaining attention, or are sensitive to motion (e.g., vestibular disorders). The ability to stop the rotation is essential(WCAG 2.2.2 "Pause, Stop, Hide").
- Keyboard-only users. If the previous/next/play-pause controls are built with
<div>elements, they cannot be operated. Use native<button>elements instead. - Screen reader users. Without labels like "1 of 3" on each slide, users cannot tell how many slides there are or which one is currently displayed. If content is continuously announced during auto-rotation, it becomes confusing — turning off
aria-liveduring rotation is key.
Live Demo (Recommended Implementation)
The carousel below follows the APG pattern. Try operating the play/pause and previous/next controls with your keyboard.
Try it: Auto-advances every 4 seconds. ⏸ to stop → ‹ › to move back and forth → Hover over or focus on the carousel to pause auto-rotation.
Tip
Adding aria-roledescription causes screen readers to announce the region as "Featured Picks, carousel" and each slide as "1 of 3, slide" (optional but improves clarity). The play/pause button label should toggle between "Stop auto-rotation" and "Start auto-rotation" to match the current state.
Keyboard Interaction
| Key | Action | Priority |
|---|---|---|
| Tab → Enter / Space (play / pause button) | Toggle auto-rotation play / pause | Required |
| Enter / Space (previous / next buttons) | Manually move to the previous / next slide (auto-rotation stops on move) | Required |
| Focus / Hover | Pause auto-rotation while focus or mouse pointer is inside the carousel | Required |
Note
The play/pause button should be placed before the slides in the DOM. This lets keyboard users stop the animation as soon as they notice it.
Required WAI-ARIA Roles & Properties
| Target | Attribute / Role | Meaning |
|---|---|---|
| Carousel container | aria-roledescription="carousel" + aria-label | Conveys that this is a carousel and provides its accessible name (optional but recommended). |
| Play/pause button | <button> + aria-label reflecting current state | Allows stopping and resuming auto-rotation (required when auto-rotation is present). |
| Previous/next buttons | <button> + aria-label | Provides a label so the purpose is conveyed even with icon-only buttons. |
| Each slide | role="group" + aria-roledescription="slide" + aria-label="1 of 3" | Conveys the total number of slides and the current position. |
| Slide container | aria-live="off | polite" | Switch to off during auto-rotation and polite when stopped or manually controlled. |
| Hidden slides | hidden | Excludes non-visible slides from screen reader output and keyboard interaction. |
Implementation: Recommended Pattern (Good)
Good / Recommended
Auto-rotating carousels must include a play/pause button andpause on focus/hover. Use native <button> elements for previous/next controls, and add position labels to each slide.
Markup:
<section class="carousel"
aria-roledescription="carousel"
aria-label="Featured Picks">
<!-- Play/pause button is required for auto-rotating carousels -->
<div class="carousel-controls">
<button type="button" aria-label="Stop auto-rotation" data-toggle>⏸</button>
<button type="button" aria-label="Previous slide" data-prev>‹</button>
<button type="button" aria-label="Next slide" data-next>›</button>
</div>
<div class="carousel-slides">
<div role="group" aria-roledescription="slide" aria-label="1 of 3">
…Slide 1…
</div>
<div role="group" aria-roledescription="slide" aria-label="2 of 3" hidden>
…Slide 2…
</div>
<div role="group" aria-roledescription="slide" aria-label="3 of 3" hidden>
…Slide 3…
</div>
</div>
</section>Playback control and pause script:
let index = 0;
let playing = true;
let timer = null;
function show(i) {
index = (i + slides.length) % slides.length;
slides.forEach((s, n) => (s.hidden = n !== index));
}
function start() {
playing = true;
toggle.setAttribute('aria-label', 'Stop auto-rotation');
// Turn off live region during rotation to prevent disruptive announcements
slidesBox.setAttribute('aria-live', 'off');
timer = setInterval(() => show(index + 1), 4000);
}
function stop() {
playing = false;
toggle.setAttribute('aria-label', 'Start auto-rotation');
// Announce changes when stopped or manually controlled
slidesBox.setAttribute('aria-live', 'polite');
clearInterval(timer);
}
toggle.addEventListener('click', () => (playing ? stop() : start()));
prev.addEventListener('click', () => { stop(); show(index - 1); });
next.addEventListener('click', () => { stop(); show(index + 1); });
// Pause auto-rotation on focus/hover
carousel.addEventListener('mouseenter', () => playing && clearInterval(timer));
carousel.addEventListener('mouseleave', () => playing && start());
carousel.addEventListener('focusin', () => playing && clearInterval(timer));
carousel.addEventListener('focusout', () => playing && start());Anti-pattern (Bad)
Below is a carousel with unstoppable auto-rotation. The arrows are <div>elements that cannot receive focus, and the slides have no position labels.
Try it: Advances every 2 seconds with no way to stop. Tab cannot focus the arrows, and screen readers cannot tell which slide is showing.
<!-- ❌ Anti-pattern -->
<div class="carousel">
<!-- No way to stop auto-rotation, no slide labels -->
<div class="slides">
<div class="slide">Feature 1</div>
<div class="slide" style="display:none">Feature 2</div>
</div>
<!-- div arrows cannot receive focus -->
<div class="arrow" onclick="prev()">‹</div>
<div class="arrow" onclick="next()">›</div>
</div>
<script>
// Unstoppable infinite auto-rotation
setInterval(next, 2000);
</script>Bad / Avoid
Problems with this implementation:
- Cannot be stopped — No play/pause button, violating WCAG 2.2.2. Unreadable for people sensitive to motion.
- No pause on focus/hover — Slides keep switching even while the user is interacting.
- Arrows are not focusable —
div+onclickcannot be operated with a keyboard. - No position information — Slides lack
role="group"and "X of Y" labels.
Implementation Checklist
- A play/pause button is provided whenever auto-rotation is used
- Auto-rotation pauses while the carousel has focus or hover
- Previous, next, and play/pause controls all use native <button> elements (not div)
- Icon buttons have aria-label, and the label toggles between stop and start states
- Each slide has role="group" with a label indicating its position (e.g. "1 of 3")
- aria-live is set to off during auto-rotation and polite when stopped or manually controlled
- Non-visible slides are hidden
- aria-roledescription="carousel" is applied (optional), and focus indicators are visible
Source (English):Carousel Pattern — W3C APG(opens in a new tab)