For moving, blinking, scrolling or auto-updating information that lasts more than five seconds, users must be able to pause, stop, or hide it.
carousel • 5000ms interval
ticker • 3000ms interval
animation • 1000ms interval
video • 30000ms interval
Important announcement about accessibility
• Pause and stop controls
• Manual navigation arrows
• Keyboard accessible
• Screen reader friendly
• Can pause updates
• Can hide completely
• Non-essential information
• User has full control
• Standard video controls
• No autoplay by default
• Audio controls available
• Respects user preferences
• Respects motion preferences
• Can be disabled
• Provides feedback alternative
• Accessible to all users
<!-- Good Example: Carousel with Controls -->
<div class="carousel-container" role="region" aria-labelledby="carousel-title">
<h3 id="carousel-title">Latest News</h3>
<div class="carousel-controls">
<button onclick="pauseCarousel()" aria-label="Pause carousel">
<span class="icon">⏸</span> Pause
</button>
<button onclick="playCarousel()" aria-label="Play carousel">
<span class="icon">▶</span> Play
</button>
<button onclick="stopCarousel()" aria-label="Stop carousel">
<span class="icon">⏹</span> Stop
</button>
</div>
<div class="carousel-content" aria-live="off" aria-atomic="true">
<div class="slide active">
<h4>Breaking News</h4>
<p>Important announcement about accessibility</p>
</div>
<div class="slide">
<h4>Technology Update</h4>
<p>New features improve user experience</p>
</div>
</div>
<div class="carousel-indicators">
<button onclick="goToSlide(0)" aria-label="Go to slide 1">1</button>
<button onclick="goToSlide(1)" aria-label="Go to slide 2">2</button>
</div>
</div>
<!-- Good Example: Ticker with Controls -->
<div class="ticker-container">
<div class="ticker-controls">
<button onclick="pauseTicker()">Pause Updates</button>
<button onclick="hideTicker()">Hide Ticker</button>
</div>
<div class="ticker-content" aria-live="polite">
<span class="ticker-item">AAPL $150.25 ↑2.5%</span>
</div>
</div>
<!-- Good Example: Video with Controls -->
<video controls preload="metadata" class="background-video">
<source src="background.mp4" type="video/mp4">
<track kind="captions" src="captions.vtt" srclang="en" label="English">
<p>Your browser doesn't support HTML5 video.
<a href="background.mp4">Download the video</a>.</p>
</video>
<!-- User Preferences -->
<div class="motion-preferences">
<h3>Motion Preferences</h3>
<label>
<input type="checkbox" onchange="setReduceMotion(this.checked)">
Reduce motion and animations
</label>
<label>
<input type="checkbox" onchange="setPauseAnimations(this.checked)">
Pause auto-playing content
</label>
<label>
<input type="checkbox" onchange="setHideFlashing(this.checked)">
Hide flashing and blinking content
</label>
</div>
<!-- Bad Example: No Controls -->
<div class="bad-carousel">
<div class="auto-slide">
<!-- Content that moves automatically with no way to stop -->
<p>This content moves automatically and cannot be controlled</p>
</div>
</div>
<script>
// Good implementation with user controls
class AccessibleCarousel {
constructor(element, options = {}) {
this.element = element;
this.slides = element.querySelectorAll('.slide');
this.currentSlide = 0;
this.isPlaying = options.autoplay !== false;
this.interval = options.interval || 5000;
this.timer = null;
this.init();
}
init() {
this.createControls();
this.setupEventListeners();
this.setupKeyboardNavigation();
if (this.isPlaying) {
this.play();
}
}
createControls() {
const controls = document.createElement('div');
controls.className = 'carousel-controls';
controls.innerHTML = `
<button class="play-pause-btn" aria-label="Pause carousel">
<span class="icon">⏸</span> Pause
</button>
<button class="stop-btn" aria-label="Stop carousel">
<span class="icon">⏹</span> Stop
</button>
<button class="prev-btn" aria-label="Previous slide">
<span class="icon">←</span> Previous
</button>
<button class="next-btn" aria-label="Next slide">
<span class="icon">→</span> Next
</button>
`;
this.element.insertBefore(controls, this.element.firstChild);
}
play() {
this.isPlaying = true;
this.timer = setInterval(() => {
this.nextSlide();
}, this.interval);
this.updatePlayButton();
this.announceToScreenReader('Carousel playing');
}
pause() {
this.isPlaying = false;
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
this.updatePlayButton();
this.announceToScreenReader('Carousel paused');
}
stop() {
this.pause();
this.goToSlide(0);
this.announceToScreenReader('Carousel stopped');
}
nextSlide() {
this.goToSlide((this.currentSlide + 1) % this.slides.length);
}
prevSlide() {
this.goToSlide((this.currentSlide - 1 + this.slides.length) % this.slides.length);
}
goToSlide(index) {
this.slides[this.currentSlide].classList.remove('active');
this.currentSlide = index;
this.slides[this.currentSlide].classList.add('active');
// Update live region
const liveRegion = this.element.querySelector('[aria-live]');
if (liveRegion) {
liveRegion.textContent = this.slides[this.currentSlide].textContent;
}
}
updatePlayButton() {
const playBtn = this.element.querySelector('.play-pause-btn');
if (this.isPlaying) {
playBtn.innerHTML = '<span class="icon">⏸</span> Pause';
playBtn.setAttribute('aria-label', 'Pause carousel');
} else {
playBtn.innerHTML = '<span class="icon">▶</span> Play';
playBtn.setAttribute('aria-label', 'Play carousel');
}
}
setupEventListeners() {
this.element.addEventListener('click', (e) => {
if (e.target.matches('.play-pause-btn')) {
this.isPlaying ? this.pause() : this.play();
} else if (e.target.matches('.stop-btn')) {
this.stop();
} else if (e.target.matches('.prev-btn')) {
this.prevSlide();
} else if (e.target.matches('.next-btn')) {
this.nextSlide();
}
});
// Pause on hover (optional)
this.element.addEventListener('mouseenter', () => {
if (this.isPlaying) {
this.pause();
}
});
this.element.addEventListener('mouseleave', () => {
if (!this.isPlaying) {
this.play();
}
});
}
setupKeyboardNavigation() {
this.element.addEventListener('keydown', (e) => {
switch(e.key) {
case 'ArrowLeft':
e.preventDefault();
this.prevSlide();
break;
case 'ArrowRight':
e.preventDefault();
this.nextSlide();
break;
case ' ':
e.preventDefault();
this.isPlaying ? this.pause() : this.play();
break;
case 'Home':
e.preventDefault();
this.goToSlide(0);
break;
case 'End':
e.preventDefault();
this.goToSlide(this.slides.length - 1);
break;
}
});
}
announceToScreenReader(message) {
const announcement = document.createElement('div');
announcement.setAttribute('aria-live', 'polite');
announcement.className = 'sr-only';
announcement.textContent = message;
document.body.appendChild(announcement);
setTimeout(() => document.body.removeChild(announcement), 1000);
}
}
// Respect user preferences
const respectMotionPreferences = () => {
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (prefersReducedMotion) {
// Pause all animations
document.querySelectorAll('.carousel').forEach(carousel => {
const instance = carousel.carouselInstance;
if (instance) {
instance.pause();
}
});
// Disable CSS animations
document.body.classList.add('reduce-motion');
}
};
// Initialize carousels
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.carousel-container').forEach(carousel => {
carousel.carouselInstance = new AccessibleCarousel(carousel);
});
respectMotionPreferences();
});
</script>Remember: Moving content can be distracting, trigger vestibular disorders, and make it difficult for users with cognitive disabilities to focus on important information.
Always provide user control over motion and respect accessibility preferences.