Skip to Content
Component ExamplesCarousels

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:

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: reduce by 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

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
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()"> &#8249; </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()"> &#8250; </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

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", and aria-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: reduce by disabling auto-play entirely

What the screen reader announces:

VersionAnnouncement
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-roledescription provides 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.

Resources