Overlays
Carousel
A set of rotating slides with controls to move between them and pause automatic rotation.
Last verified against WCAG 2.2 and WAI-ARIA APG 1.2 on 2026-07-02.
Implementation
New: Team workspaces
Share projects with your whole team in one shared space.
carousel-pattern.tsx
function CarouselPattern() {
const [index, setIndex] = useState(0);
const [playing, setPlaying] = useState(false); // off by default
const [announcement, setAnnouncement] = useState("");
function goTo(next, userInitiated) {
const wrapped = (next + SLIDES.length) % SLIDES.length;
setIndex(wrapped);
if (userInitiated) {
// Never announce pure autoplay ticks — only user-driven changes.
setAnnouncement(`Slide ${wrapped + 1} of ${SLIDES.length}: ${SLIDES[wrapped].title}`);
}
}
useEffect(() => {
if (!playing) return;
const id = setInterval(() => setIndex(i => (i + 1) % SLIDES.length), 5000);
return () => clearInterval(id);
}, [playing]);
return (
<div role="region" aria-roledescription="carousel" aria-label="Product announcements"
onKeyDown={e => {
if (e.key === "ArrowRight") goTo(index + 1, true);
if (e.key === "ArrowLeft") goTo(index - 1, true);
}}>
<div aria-roledescription="slide" aria-label={`${index + 1} of ${SLIDES.length}`}>
{SLIDES[index].title}
</div>
<div aria-live="polite" aria-atomic="true" className="sr-only">{announcement}</div>
<button onClick={() => goTo(index - 1, true)} aria-label="Previous slide">‹</button>
<button onClick={() => goTo(index + 1, true)} aria-label="Next slide">›</button>
<button onClick={() => setPlaying(p => !p)} aria-pressed={playing}>
{playing ? "Pause" : "Play"}
</button>
</div>
);
}Live demo
New: Team workspaces
Share projects with your whole team in one shared space.
Required roles, states & properties
| Element | Attribute | Why |
|---|---|---|
| Carousel wrapper | role="region" + aria-roledescription="carousel" + aria-label | There is no native ARIA carousel role, so region + roledescription gives AT a landmark plus a more specific spoken name ("carousel" instead of generic "region"). |
| Each slide | aria-roledescription="slide" + aria-label="N of M" | Announces each slide's position, so screen reader users always know where they are, independent of visual pagination dots. |
| Live region | aria-live="polite" + aria-atomic="true", visually hidden | Announces the new slide's title after a user-initiated change. Kept silent during autoplay ticks so it doesn't interrupt users every few seconds. |
| Pause/Play button | aria-pressed | Exposes autoplay state as a toggle button. Must always be present and operable whenever autoplay can run — required by SC 2.2.2. |
| Previous / Next buttons | aria-label="Previous slide" / "Next slide" | Gives icon-only buttons an accessible name, since a chevron glyph alone has none. |
Keyboard interaction model
| Key | Behavior |
|---|---|
| Tab / Shift+Tab | Moves through the active slide's content, then the Previous/Next/Pause controls, then out to the next page element. Inactive slides are never in the tab order. |
| Left / Right Arrow (focus within region) | Moves to the previous / next slide. Also acceptable per APG to rely on the dedicated Previous/Next buttons alone, operable via Tab + Enter. |
| Enter / Space (on Previous, Next, or Pause/Play button) | Activates that control. |
Focus management rules
- Only the active slide's content is in the Tab order — inactive slides are never reachable, avoiding focus landing on off-screen content.
- Tab moves through the active slide, then the Previous/Next/Pause controls, then continues to the next element on the page — the region never traps focus.
- Changing slides via a control never forcibly moves keyboard focus away from that control.
WCAG 2.2 success criteria mapping
| SC | Name | Level | Why it applies |
|---|---|---|---|
| 2.1.1 | Keyboard | A | All functionality is operable through a keyboard interface with no specific timing. |
| 2.2.2 | Pause, Stop, Hide | A | Moving, blinking, or auto-updating content lasting more than 5 seconds can be paused, stopped, or hidden. |
| 2.4.3 | Focus Order | A | Focusable components receive focus in an order that preserves meaning and operability. |
| 4.1.2 | Name, Role, Value | A | For all UI components, name, role, and value are programmatically determinable; states and changes are announced. |