Overview
Carousels (also called slideshows or sliders) are one of the most common — and most accessibility-challenged — patterns on the web. Auto-rotating content creates problems for screen reader users who lose context, keyboard users who cannot reach controls, motor-impaired users who cannot pause movement, and users with cognitive disabilities who find auto-advancing content disorienting. WCAG 2.2.2 requires that any auto-playing content must be pausable, stoppable, or hideable.
WCAG Criteria:
- 2.2.2 Pause, Stop, Hide — auto-playing content must provide a mechanism to pause, stop, or hide it
- 1.3.1 Info and Relationships — slide structure and position must be programmatically conveyed
- 4.1.2 Name, Role, Value — carousel controls and regions must have accessible names and roles
Key requirements:
- Auto-rotating content must have a visible Pause/Play button
- Carousel container and slides need appropriate ARIA roles (
aria-roledescription) - Slide position must be announced (e.g. “Slide 1 of 3”)
- Auto-rotation must pause on hover and on keyboard focus
- Respect
prefers-reduced-motion: reduceby disabling auto-play entirely - Tab order should flow logically: Previous, slide content, Next, Pause
- Consider whether a carousel is even the right pattern for your content
Carousel
Inaccessible Auto-Play vs. Accessible Carousel
Inaccessible
<!-- Auto-playing carousel — no pause, no ARIA, dot-only navigation -->
<div class="carousel">
<div class="slide" style="display:flex;">Slide 1</div>
<div class="slide" style="display:none;">Slide 2</div>
<div class="slide" style="display:none;">Slide 3</div>
<!-- Dot indicators only — no labels, not focusable -->
<div class="dots">
<span class="dot active"></span>
<span class="dot"></span>
<span class="dot"></span>
</div>
</div>
<script>
// Auto-rotates every 3 seconds — no way to pause
let current = 0;
setInterval(() => {
current = (current + 1) % 3;
slides.forEach((s, i) =>
s.style.display = i === current ? 'flex' : 'none'
);
}, 3000);
</script>Live Preview
Slide 1
Accessible
<!-- Accessible carousel with ARIA, pause, and keyboard support -->
<div
aria-roledescription="carousel"
aria-label="Featured content"
onmouseenter="pause()"
onmouseleave="resumeIfNotUserPaused()"
onfocusin="pause()"
onfocusout="resumeIfNotUserPaused()"
>
<button aria-label="Previous slide" onclick="prevSlide()">
‹
</button>
<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>
<button aria-label="Next slide" onclick="nextSlide()">
›
</button>
<button
aria-label="Pause auto-rotation"
onclick="togglePause()"
>
Pause
</button>
<!-- Live region announces slide changes -->
<div aria-live="polite" aria-atomic="true"
class="visually-hidden">
Slide 1 of 3
</div>
</div>
<script>
// Respects prefers-reduced-motion
const reducedMotion =
matchMedia('(prefers-reduced-motion: reduce)').matches;
if (reducedMotion) {
// Don't auto-rotate at all
userPaused = true;
}
</script>Live Preview
Slide 1
Slide 1 of 3
What’s wrong with the inaccessible carousel?
- Auto-rotates every 3 seconds with no pause button — violates WCAG 2.2.2
- Navigation is limited to small dot indicators that are
<span>elements — not focusable, no labels, unreachable by keyboard - No ARIA roles — screen readers cannot identify the component as a carousel or individual slides
- No slide position information — users have no idea where they are in the sequence
- No way to stop or slow the auto-rotation for users who need more reading time
- Continues rotating even when the user is trying to interact with the content
What the accessible carousel provides:
aria-roledescription="carousel"on the container identifies the component to screen readers- Each slide has
role="group",aria-roledescription="slide", andaria-label="X of 3"for position info - Previous/Next buttons provide keyboard-accessible navigation with clear labels
- Pause/Play button satisfies WCAG 2.2.2 — users can stop auto-rotation at any time
aria-live="polite"region announces the current slide position during manual navigation- Auto-rotation pauses on hover (
onmouseenter) and keyboard focus (onfocusin) - Respects
prefers-reduced-motion: reduceby disabling auto-play entirely
What the screen reader announces:
| Version | Announcement |
|---|---|
| Inaccessible carousel | ”Slide 1” (just text — no context, no position, no controls) |
| Accessible carousel | ”Featured content, carousel” then “Slide 1, 1 of 3, slide” with “Previous slide / Next slide / Pause” buttons |
Key Teaching Points
- WCAG 2.2.2 is non-negotiable — any auto-playing, auto-rotating, or auto-advancing content must be pausable, stoppable, or hideable. A carousel without a pause button is a WCAG failure.
- Respect
prefers-reduced-motion: reduce— users who enable this OS-level setting are telling you they need reduced or no animation. Disable auto-rotation entirely for these users. - Tab order matters — the logical flow should be: Previous button, slide content (if interactive), Next button, Pause button. This keeps navigation predictable.
aria-roledescriptionprovides context — using “carousel” on the container and “slide” on each panel tells screen reader users what type of component they are interacting with, without overriding the base semantics.- Consider whether a carousel is even necessary — studies consistently show that users rarely interact with carousel content beyond the first slide. A static hero or a simple list may be more effective and far easier to make accessible.