Carousels zijn populair maar vaak een toegankelijkheidsnachtmerrie. Bewegende content, automatisch afspelen, onduidelijke navigatie: Het kan allemaal fout gaan. Maar het kan ook goed!
De belangrijkste regel: gebruikers moeten de carousel kunnen pauzeren. Bewegende content is voor veel mensen storend of zelfs onmogelijk te lezen. Een pauzeknop is dus een must.
Elke slide moet:
- Een duidelijk label hebben (Slide 1 van 5)
- Met het toetsenbord bereikbaar zijn
- Aangeven welke slide actief is
Technische opbouw
- Geef de container de rol
section
- Geef de container een naam (via
aria-labelledby
ofaria-label
) - Geef de container een rolbeschrijving
aria-roledescription="carousel"
- Groepeer elke slide met
role="group"
- Geef elke slide een naam (via
aria-labelledby
ofaria-label
) - Geef elke slide een rolbeschrijving
aria-roledescription="slide"
- Voor huidige slide:
- Geef de slide de waarde
aria-current="slide"
- Geef de slide de waarde
- Maak elke knop op een
<button>
-element - Geef elke knop een naam (via
aria-labelledby
ofaria-label
)
Voorbeeld carrousel
HTML
<section class="carousel" role="section" aria-label="Productfoto's" aria-roledescription="carousel">
<div class="carousel-slides" id="carousel-slides">
<div class="carousel-slide active" role="group" aria-current="slide" aria-label="Slide 1 van 3" aria-roledescription="slide">
<h3>Slide 1</h3>
<p>Dit is de eerste slide met belangrijke informatie.</p>
</div>
<div class="carousel-slide" role="group" aria-label="Slide 2 van 3" aria-roledescription="slide">
<h3>Slide 2</h3>
<p>Dit is de tweede slide met meer details.</p>
</div>
<div class="carousel-slide" role="group" aria-label="Slide 3 van 3" aria-roledescription="slide">
<h3>Slide 3</h3>
<p>Dit is de derde en laatste slide.</p>
</div>
</div>
<div class="carousel-controls">
<button class="carousel-prev" aria-controls="carousel-slides" aria-label="Vorige slide">
Vorige
</button>
<button class="carousel-pause" aria-label="Pauzeer autoplay">
Pauzeer
</button>
<button class="carousel-next" aria-controls="carousel-slides" aria-label="Volgende slide">
Volgende
</button>
</div>
<div class="carousel-nav" role="group" aria-label="Slide knoppen">
<button class="carousel-indicator active" aria-controls="carousel-slides" aria-label="Ga naar slide 1"></button>
<button class="carousel-indicator" aria-controls="carousel-slides" aria-label="Ga naar slide 2"></button>
<button class="carousel-indicator" aria-controls="carousel-slides" aria-label="Ga naar slide 3"></button>
</div>
</section>
CSS
/* Carrousel stijlen */
.carousel {
position: relative;
max-width: 600px;
margin: 0 auto;
}
.carousel-slides {
position: relative;
overflow: hidden;
border-radius: 4px;
}
.carousel-slide {
padding: 2rem;
background: #f0f8ff;
border: 1px solid black;
text-align: center;
display: none;
border-radius: 4px;
}
.carousel-slide.active {
display: block;
}
/* Belangrijke toegankelijkheidsregel: verborgen slides niet focusbaar */
.carousel-slide:not(.active) * {
visibility: hidden;
}
.carousel-controls {
display: flex;
justify-content: center;
gap: 1rem;
margin-top: 1rem;
}
.carousel-controls button {
padding: 1rem;
background: black;
color: white;
border: none;
font-size: 0.9rem;
}
.carousel-controls button:hover,
.carousel-controls button:focus {
color: black;
background: white;
}
.carousel-nav {
display: flex;
justify-content: center;
gap: 0.5rem;
margin-top: 1rem;
}
.carousel-indicator {
width: 12px;
height: 12px;
border-radius: 50%;
border: 2px solid black;
}
.carousel-indicator:hover,
.carousel-indicator:focus {
outline: 2px solid #ff6600;
outline-offset: 2px;
}
.carousel-indicator.active {
background: #0066cc;
}
/* Screen reader only content */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
JavaScript
// Carrousel functionaliteit
class Carousel {
constructor() {
this.carousel = document.querySelector('.carousel');
this.slides = document.querySelectorAll('.carousel-slide');
this.indicators = document.querySelectorAll('.carousel-indicator');
this.prevBtn = document.querySelector('.carousel-prev');
this.nextBtn = document.querySelector('.carousel-next');
this.pauseBtn = document.querySelector('.carousel-pause');
this.status = document.getElementById('carousel-status');
this.currentSlide = 0;
this.isPlaying = false;
this.autoplayInterval = null;
this.init();
}
init() {
// Event listeners voor knoppen
this.prevBtn.addEventListener('click', () => this.prevSlide());
this.nextBtn.addEventListener('click', () => this.nextSlide());
this.pauseBtn.addEventListener('click', () => this.toggleAutoplay());
// Event listeners voor indicatoren
this.indicators.forEach((indicator, index) => {
indicator.addEventListener('click', () => this.goToSlide(index));
});
// Toetsenbord navigatie
this.carousel.addEventListener('keydown', (e) => {
if (e.key === 'ArrowLeft') {
e.preventDefault();
this.prevSlide();
} else if (e.key === 'ArrowRight') {
e.preventDefault();
this.nextSlide();
}
});
// Pauzeer bij mouse hover
this.carousel.addEventListener('mouseenter', () => this.pause());
this.carousel.addEventListener('mouseleave', () => this.resume());
// Pauzeer bij focus
this.carousel.addEventListener('focusin', () => this.pause());
this.carousel.addEventListener('focusout', () => this.resume());
this.updateStatus();
}
goToSlide(index) {
// Verwijder actieve status van huidige slide
this.slides[this.currentSlide].classList.remove('active');
this.slides[this.currentSlide].removeAttribute('aria-current');
this.indicators[this.currentSlide].classList.remove('active');
// Stel nieuwe slide in
this.currentSlide = index;
// Voeg actieve status toe aan nieuwe slide
this.slides[this.currentSlide].classList.add('active');
this.slides[this.currentSlide].setAttribute('aria-current', 'slide');
this.indicators[this.currentSlide].classList.add('active');
this.updateStatus();
}
nextSlide() {
const next = (this.currentSlide + 1) % this.slides.length;
this.goToSlide(next);
}
prevSlide() {
const prev = (this.currentSlide - 1 + this.slides.length) % this.slides.length;
this.goToSlide(prev);
}
toggleAutoplay() {
if (this.isPlaying) {
this.stopAutoplay();
} else {
this.startAutoplay();
}
}
startAutoplay() {
this.isPlaying = true;
this.pauseBtn.textContent = 'Pauzeer';
this.pauseBtn.setAttribute('aria-label', 'Pauzeer autoplay');
this.autoplayInterval = setInterval(() => {
this.nextSlide();
}, 4000);
}
stopAutoplay() {
this.isPlaying = false;
this.pauseBtn.textContent = 'Speel af';
this.pauseBtn.setAttribute('aria-label', 'Start autoplay');
if (this.autoplayInterval) {
clearInterval(this.autoplayInterval);
this.autoplayInterval = null;
}
}
pause() {
if (this.autoplayInterval) {
clearInterval(this.autoplayInterval);
}
}
resume() {
if (this.isPlaying && !this.autoplayInterval) {
this.autoplayInterval = setInterval(() => {
this.nextSlide();
}, 4000);
}
}
}
// Start de carrousel
document.addEventListener('DOMContentLoaded', () => {
new Carousel();
});
Toetsenbordbediening
- Tab:
- Gaat naar het volgende focusbare element in de carrousel
- Shift + Tab:
- Gaat naar het vorige focusbare element in de carrousel
Optionele toetsen
- Pijltjestoetsen:
- Pijl recht: gaat naar de volgende slide
- Pijl links: gaat naar de vorige slide
Focus management
- Zet rotation control altijd vóór de slides in tab-volgorde
- De focus mag niet op verborgen slides terechtkomen
Checklist
Component
- (4.1.2) De carrousel-container moet de rol
section
hebben - (4.1.2) De carrousel-container moet een toegankelijke naam hebben
- (2.4.6) De toegankelijke naam moet beschrijven wat er in de carrousel staat
- (4.1.2) De carrousel-container zou
aria-roledescription="carousel"
moeten hebben - (4.1.2) Elke slide moet
role="group"
hebben - (4.1.2) Elke slide moet een toegankelijke naam hebben
- (2.4.6) De toegankelijke naam zou de positie-informatie moeten hebben (“1 van 6”, “2 van 6”, enz.)
- (4.1.2) Elke slide zou
aria-roledescription="slide"
moeten hebben - (4.1.2) De huidige slide moet
aria-current="slide"
hebben - (4.1.2) Alle knoppen moeten een
<button>
-element zijn - (4.1.2) Alle knoppen moeten een toegankelijke naam hebben
- (2.4.6) De toegankelijke naam moet beschrijven wat het resultaat van de actie is
- (4.1.2) Navigatie-knoppen moeten
aria-controls
hebben met hetid
van de slides-container - (4.1.3) Bij automatische rotatie moet de slides-container
aria-live="off"
hebben - (4.1.3) Bij handmatige navigatie moet de slides-container
aria-live="polite"
hebben