Skip to Content
Component ExamplesCarousels

Carousels

Auto-rotating content with slide navigation — one of the most accessibility-challenged patterns on the web

Maintained by Martin KrugerLast reviewed

Overview

Carousels can create problems when users lose context, cannot reach controls, or cannot pause movement. WCAG 2.2.2 applies when moving content starts automatically, lasts more than five seconds, and appears in parallel with other content, unless the movement is essential. Auto-updating information has a related but distinct condition. A visible pause control is the most robust design for an auto-rotating carousel.

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

View inaccessible code
<!-- 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>
View accessible code
<!-- Accessible carousel with ARIA, pause, and keyboard support --> <div role="region" 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>

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

Expected semantics:

VersionProgrammatic result
Inaccessible carouselExposes slide text without a carousel region, position, or operable controls.
Accessible carouselExposes a named region with the carousel role description, grouped slides with position labels, and named Previous, Next, and Pause actions.

Key Teaching Points

  • Apply WCAG 2.2.2 precisely — qualifying non-essential movement needs a pause, stop, or hide mechanism; record how the five-second and parallel-presentation conditions apply.
  • 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