Lesson 23 / Components
Carousels
Design carousels carefully, prioritize manual controls, and build accessible slide navigation when a carousel is truly useful.
Course Role
Stretch
Good for students who are ready to go further after the core version works.
Teacher Notes / In-Class Use
Demo Live
- Use the carousel lab to show that controls, pagination, status text, keyboard support, and motion settings are separate concerns.
- Show the Splide configuration first, then explain what the hidden teaching JavaScript adds for the interactive lab.
Try In Class
- Have students identify whether their carousel needs previous and next buttons, dots, autoplay, or none of the above.
- Ask students to test the carousel with keyboard only before styling it.
Submit Or Check
- There should be a visible way to move between slides without relying on autoplay.
- If autoplay exists, students should include a pause strategy and avoid surprising motion.
Watch For
- All slides being visible because the carousel CSS or initialization did not load.
- Autoplay treated as decoration while keyboard and status feedback are ignored.
Learning Goals
- Decide when a `carousel` is appropriate and when another pattern is better
- Identify the parts of an accessible carousel, including slides, buttons, status text, and pagination
- Build manual previous/next behavior before adding optional autoplay
- Test carousel behavior with keyboard, reduced motion, screen reader expectations, and responsive layouts
Interactive Demo
How to use this demo.
Use the demo as a small lab. Change one thing, observe the result, then connect it back to your own project.
What To Try
- Toggle controls, dots, visible status, keyboard support, autoplay, and pause behavior.
- Use the previous and next buttons, dots, and arrow keys to change slides.
What Changes
- The carousel preview, checklist, and Splide code sample update together.
- Removing controls or status makes the interaction harder to understand.
What To Notice
- A carousel is not only slides. It needs controls, state, keyboard support, and motion decisions.
- Autoplay increases responsibility because users need ways to pause or control motion.
Apply It
- Check your carousel for visible controls, one active slide, keyboard access, and a pause strategy if autoplay is enabled.
Interactive Demo
Carousel Responsibility Lab
Carousels are more than moving slides. Toggle the expected controls and watch the accessibility checklist and Splide starter code change.
Slide 1 of 3
- Controls: Pass
- Pagination: Pass
- Status: Pass
- Keyboard: Pass
- Motion: Pass
new Splide('.work-slideshow', {
pagination: true,
arrows: true,
autoplay: false
}).mount(); What to notice
- Users need a way to move forward, move backward, and understand where they are.
- Autoplay creates motion and timing problems if it cannot pause.
- Keyboard support matters because carousel buttons are real controls.
Try this
- Turn autoplay on, then turn pause behavior off and inspect the checklist.
- Focus the carousel and use the left and right arrow keys.
- Hide the status text and explain what context disappears.
This demo uses extra JavaScript for teaching. The code sample shows the pattern to practice. View full demo source.
What Are Carousels?
A carousel displays one item or group of items at a time from a larger set. Users move through the items with controls such as previous and next buttons, pagination dots, or sometimes autoplay.
Carousels can be useful, but they can also hide content, distract users, and create accessibility problems. Use one only when it helps the user browse a small set of related content.
Should You Use a Carousel?
| Situation | Recommendation | Why |
|---|---|---|
| A small set of related images or testimonials | Maybe use a carousel | Users can browse a focused set manually. |
| Critical content users must see | Avoid a carousel | Hidden slides may never be seen. |
| Many cards or products | Use a grid or listing page | Browsing and comparison are easier when more items are visible. |
| Content users need to read carefully | Avoid autoplay | Changing slides can interrupt reading. |
| A homepage hero with one main message | Use a static hero | One strong message is clearer than rotating messages. |
Better Alternatives
Before choosing a carousel, consider whether a simpler pattern would serve users better.
| Alternative | Use When |
|---|---|
| Card grid | Users need to scan or compare several items. |
| Featured content section | One or two items deserve emphasis. |
| Tabs | A small set of related panels should be directly selectable. |
| Accordion | Content should expand and collapse in place. |
| Static hero with links | The first screen needs one clear message and next step. |
Carousel Anatomy
| Part | Purpose |
|---|---|
| Region or container | Groups the carousel and gives it an accessible name. |
| Slides | Hold the image, text, or content item being shown. |
Previous and next button controls | Let users move manually through slides. |
| Slide position text | Communicates status, such as Slide 2 of 4. |
| Pagination buttons | Let users jump to a specific slide. |
| Pause/play control | Required if autoplay is used. |
Accessible Carousel Markup
Start with semantic markup and manual controls. Use real button elements for actions. Hide inactive slides with hidden so their links and buttons are not tabbable.
<section class="carousel" aria-label="Featured projects" aria-roledescription="carousel">
<p class="carousel-status" aria-live="polite">Slide 1 of 3</p>
<div class="carousel-slides">
<article class="slide" aria-label="Slide 1 of 3">
<img
src="project-dashboard.webp"
width="900"
height="600"
alt="Dashboard interface with charts and project cards"
>
<h2>Portfolio Dashboard</h2>
<p>A responsive dashboard for tracking project progress.</p>
</article>
<article class="slide" aria-label="Slide 2 of 3" hidden>
<h2>Course Landing Page</h2>
<p>A landing page with pricing, testimonials, and enrollment CTA.</p>
</article>
<article class="slide" aria-label="Slide 3 of 3" hidden>
<h2>Gallery Experience</h2>
<p>An image-heavy project page with optimized media.</p>
</article>
</div>
<button class="carousel-prev" type="button">Previous</button>
<button class="carousel-next" type="button">Next</button>
</section> Basic Carousel CSS
Keep dimensions stable so the page does not jump between slides. Make controls large enough for mouse, keyboard, and touch users.
.carousel {
display: grid;
gap: 1rem;
}
.slide img {
aspect-ratio: 3 / 2;
width: 100%;
height: auto;
object-fit: cover;
}
.carousel-prev,
.carousel-next {
min-inline-size: 2.75rem;
min-block-size: 2.75rem;
}
.carousel button:focus-visible {
outline: 3px solid currentColor;
outline-offset: 0.25rem;
} Manual JavaScript First
Build manual previous and next behavior before adding autoplay. Users should always be able to control the carousel themselves.
const carousel = document.querySelector('.carousel');
const slides = [...carousel.querySelectorAll('.slide')];
const status = carousel.querySelector('.carousel-status');
const previousButton = carousel.querySelector('.carousel-prev');
const nextButton = carousel.querySelector('.carousel-next');
let currentIndex = 0;
function showSlide(index) {
currentIndex = (index + slides.length) % slides.length;
slides.forEach((slide, slideIndex) => {
slide.hidden = slideIndex !== currentIndex;
});
status.textContent = 'Slide ' + (currentIndex + 1) + ' of ' + slides.length;
}
previousButton.addEventListener('click', () => {
showSlide(currentIndex - 1);
});
nextButton.addEventListener('click', () => {
showSlide(currentIndex + 1);
}); Pagination Buttons
Pagination buttons let users jump directly to a slide. Use aria-current="true" on the active pagination button.
<div class="carousel-pagination" aria-label="Choose slide">
<button type="button" aria-current="true">1</button>
<button type="button">2</button>
<button type="button">3</button>
</div> Optional Autoplay Rules
Autoplay should be optional, easy to pause, and respectful of user motion preferences. Do not autoplay content users need to read carefully.
- Provide a pause/play
buttonif slides advance automatically. - Pause autoplay when the carousel receives keyboard focus.
- Pause autoplay on pointer hover.
- Use
clearIntervalwhen pausing or destroying autoplay. - Do not autoplay when
prefers-reduced-motionis set toreduce.
Autoplay with Guardrails
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
let autoplayId;
function startAutoplay() {
if (reduceMotion || autoplayId) return;
autoplayId = setInterval(() => {
showSlide(currentIndex + 1);
}, 5000);
}
function stopAutoplay() {
clearInterval(autoplayId);
autoplayId = undefined;
}
carousel.addEventListener('mouseenter', stopAutoplay);
carousel.addEventListener('focusin', stopAutoplay); Keyboard Behavior
Tabshould reach the carousel controls and any interactive content in the visible slide.- Inactive slides should not contain tabbable links or buttons. Use
hiddenor manage focus carefully. - Arrow key shortcuts are optional. Do not require them for basic use.
- Focus should not jump unexpectedly when the slide changes.
- The carousel should remain understandable if JavaScript fails.
Responsive and Image Guidance
- Use consistent image dimensions or aspect ratios to prevent layout shift.
- Give informative images meaningful
alttext and decorative images emptyalt="". - Keep controls reachable on touch screens.
- Do not lock the carousel to a height that causes text overflow on small screens.
- Do not lazy-load the first visible slide image if it is above the fold.
Modern Library Option: Splide
Splide is a modern carousel library that works without jQuery. Use it when a project needs a polished carousel faster than you can build and test one from scratch.
The same rules still apply: keep controls visible, avoid autoplay by default, write useful slide content, and test keyboard, screen reader, motion, and responsive behavior.
A library can save time, but it does not make the carousel automatically accessible or appropriate for the page.
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@splidejs/splide@4/dist/css/splide.min.css">
<section class="splide" aria-label="Featured projects">
<div class="splide__track">
<ul class="splide__list">
<li class="splide__slide">Project 1</li>
<li class="splide__slide">Project 2</li>
<li class="splide__slide">Project 3</li>
</ul>
</div>
</section>
<script src="https://cdn.jsdelivr.net/npm/@splidejs/splide@4/dist/js/splide.min.js"></script>
<script>
new Splide('.splide', {
arrows: true,
pagination: true,
autoplay: false,
}).mount();
</script> Common Mistakes
| Mistake | Why It Hurts | Fix |
|---|---|---|
| Autoplay with no pause | Users lose control of moving content | Add pause/play and pause on focus or hover. |
| Using a carousel for important content | Hidden slides may never be seen | Put critical content directly on the page. |
| Tiny controls | Controls are hard to use on touch screens | Use large, visible button controls. |
| Controls only appear on hover | Keyboard and touch users may not find them | Keep controls visible or reveal them on focus too. |
| Hidden slides are still tabbable | Keyboard users can tab into invisible content | Use hidden or remove inactive content from the tab order. |
| Announcements are too aggressive | Screen reader users may be interrupted | Use aria-live="polite" carefully and avoid constant autoplay announcements. |
| Images have inconsistent sizes | The page jumps between slides | Use stable dimensions and aspect ratios. |
Checkpoint
Before moving on, make sure these feel true.
- I can explain what controls, dots, status text, and autoplay each do.
- I can verify only one carousel slide is visually active at a time.
- I can test carousel navigation without relying on a mouse.
Project Connection
This lesson supports current class projects.
Practice
- Build static carousel markup with three slides.
- Add previous and next
buttoncontrols. - Hide inactive slides with
hidden. - Add slide count text such as
Slide 1 of 3. - Add optional pagination buttons with
aria-current="true". - Add pause/play only if you add autoplay.
- Test the carousel with keyboard navigation and reduced motion settings.