
If we want to show off our content in a sequence then slideshows are the only game in town. Think of things like hero banners, product tours or testimonials; they really pop when they’re nestled within a well built slider that loads fast and works for everyone.
In this tutorial, we’ll build a simple JavaScript slideshow with smooth transitions that has keyboard navigation and proper ARIA (Accessible Rich Internet Applications) attributes. We’ll also connect it to Cloudinary for optimized image delivery, making sure that our slides load quickly on different devices without messing with the quality of the images.
Key Takeaways
- Slideshows need semantic HTML and ARIA roles for accessibility
- CSS transitions handle animations and JavaScript manages state
- Autoplay should pause on hover and also respect reduced movement settings
In this article:
- What a JavaScript Slideshow Is (and When to Use It)
- HTML Setup
- CSS: Layout, Transitions, Responsive
- JS Controls: Next/Prev, Dots, Autoplay
- Accessibility: ARIA, Focus, Keyboard
- Use Cloudinary for Slideshow Images
What a JavaScript Slideshow Is (and When to Use It)
A slideshow is a feature that cycles through content. They’re different to image viewers because image viewers just display one item at a time. Slideshows usually have features like autoplay and focus on visual storytelling with a selection of images.
Slideshows for Storytelling, Product Tours, and Case Studies
Some examples are:
- Hero slideshows introduce brands with messages or slogans
- Product tours that walk users through different features step by step
- Case studies and testimonials that cycle through social proof
With these examples, the slideshow controls the pace, and also gives users the option to navigate manually if they want to.
But we have to be choosy with our selection. Too many slides dilute the impact of the presentation and users will lose interest. Scroll too fast and users won’t have a chance to figure out what is going on.
The best way for us to avoid this is to build controls that let users take over when they want to.
HTML Setup
We start with some semantic markup, and the slideshow container that holds the slides and controls:
<div class="slideshow" role="region" aria-label="Featured content">
<div class="slides">
<div class="slide active" role="group" aria-label="Slide 1 of 3">
<img src="" alt="Product feature overview">
<div class="slide-content">
<h2>Welcome to Our Platform</h2>
<p>Build faster with modern tools.</p>
</div>
</div>
<!-- More slides -->
</div>
<div class="controls">
<button class="prev" aria-label="Previous slide"><</button>
<div class="dots"></div>
<button class="next" aria-label="Next slide">></button>
</div>
</div>
Each slide gets role="group" with a label to show its position. The container uses role="region" so screen readers can announce it as a distinct section for accessibility.
CSS: Layout, Transitions, Responsive
We stack slides using absolute positioning and we toggle visibility with opacity:
.slideshow {
position: relative;
overflow: hidden;
}
.slides {
position: relative;
height: 400px;
}
.slide {
position: absolute;
inset: 0;
opacity: 0;
transition: opacity 0.5s ease;
}
.slide.active {
opacity: 1;
z-index: 1;
}
For responsive height, we can use aspect ratio or viewport units:
.slides {
aspect-ratio: 16 / 9;
height: auto;
}
.slide img {
width: 100%;
height: 100%;
object-fit: cover;
}
The object-fit: cover allows images to fill the frame without affecting image quality, and sizing is handled on the server side with Cloudinary transforms.
JS Controls: Next/Prev, Dots, Autoplay
JavaScript tracks the current index and handles navigation:
const slideshow = {
slides: [],
currentIndex: 0,
autoplayInterval: null,
init(container) {
this.container = container;
this.slides = container.querySelectorAll('.slide');
this.bindEvents();
this.createDots();
this.startAutoplay();
},
goTo(index) {
this.slides[this.currentIndex].classList.remove('active');
this.currentIndex = (index + this.slides.length) % this.slides.length;
this.slides[this.currentIndex].classList.add('active');
this.updateDots();
this.updateAriaLabels();
},
next() { this.goTo(this.currentIndex + 1); },
prev() { this.goTo(this.currentIndex - 1); }
};
The modulo operation in goTo() handles wrapping, so going past the last slide returns to the first.
Autoplay runs on an interval but pauses when users hover:
startAutoplay(delay = 5000) {
this.autoplayInterval = setInterval(() => this.next(), delay);
},
stopAutoplay() {
clearInterval(this.autoplayInterval);
},
bindEvents() {
this.container.addEventListener('mouseenter', () => this.stopAutoplay());
this.container.addEventListener('mouseleave', () => this.startAutoplay());
}
Dot indicators show position and allow direct navigation:
Accessibility: ARIA, Focus, Keyboard
Website accessibility is vital for the modern web, and accessible slideshows need more than ARIA labels. We add keyboard navigation and respect motion preferences for a better user experience.
Keyboard support lets users navigate without a mouse:
document.addEventListener('keydown', (e) => {
if (e.key === 'ArrowRight') slideshow.next();
if (e.key === 'ArrowLeft') slideshow.prev();
});
For users who prefer reduced motion, we can disable transitions:
@media (prefers-reduced-motion: reduce) {
.slide {
transition: none;
}
}
We also update ARIA attributes when slides change so screen readers announce the new content:
updateAriaLabels() {
this.slides.forEach((slide, i) => {
slide.setAttribute('aria-hidden', i !== this.currentIndex);
slide.setAttribute('aria-label', `Slide ${i + 1} of ${this.slides.length}`);
});
}
Use Cloudinary for Slideshow Images
Slideshows usually live in prominent positions like hero sections or above the fold. Images that take a while to load can hurt how users think about the performance of our sites, and slow is bad. Cloudinary solves this with automatic optimization and responsive delivery of images.
Serve Pre-Cropped Variants for Each Slide Breakpoint
Different screen sizes need different image dimensions, so we can define breakpoints and request appropriately sized images:
function getSlideUrl(publicId, width) {
return `https://res.cloudinary.com/your-cloud/image/upload/w_${width},h_${Math.round(width * 9/16)},c_fill,g_auto/${publicId}`;
}
// Desktop: 1200px wide
// Tablet: 800px wide
// Mobile: 500px wide
Combining the c_fill crop with g_auto tells images to fill the exact dimensions that we want while focusing on the most important content. For slides with people, g_face keeps faces visible.
Use q_auto and f_auto to Keep Slides Fast and Sharp
Always include q_auto and f_auto in slideshow URLs:
function getOptimizedSlideUrl(publicId, width) {
const height = Math.round(width * 9 / 16);
return `https://res.cloudinary.com/your-cloud/image/upload/w_${width},h_${height},c_fill,g_auto,q_auto,f_auto/${publicId}`;
}
These parameters let Cloudinary choose the optimal quality level and format (such as WebP, AVIF, or others) based on browser support. A 1200px hero image that might be 500KB as JPEG can drop to under 100KB with these optimizations.
For the complete slideshow, we build responsive image gallery sources:
<img srcset="
https://res.cloudinary.com/.../w_500,h_281,c_fill,g_auto,q_auto,f_auto/hero.jpg 500w,
https://res.cloudinary.com/.../w_800,h_450,c_fill,g_auto,q_auto,f_auto/hero.jpg 800w,
https://res.cloudinary.com/.../w_1200,h_675,c_fill,g_auto,q_auto,f_auto/hero.jpg 1200w"
sizes="100vw"
src="https://res.cloudinary.com/.../w_1200,h_675,c_fill,g_auto,q_auto,f_auto/hero.jpg"
alt="Hero image">
The browser chooses the best size automatically based on the viewport width.
Wrapping Up
A good slideshow balances visual impact with performance and accessibility. We built controls that work with a keyboard and mouse, and transitions that respect motion preferences. We also added image delivery that adapts to every screen size, which makes our slideshow quite versatile.
Cloudinary handles the resizing and cropping for us, and format optimization happens automatically with URL parameters. No preprocessing, no multiple file versions to manage, no hassle. To start building optimized slideshows with your own images, sign up for a free Cloudinary account.
Frequently Asked Questions
How many slides should a slideshow have?
Three to five slides works best for most scenarios. More than five and engagement starts to drop; users don’t like waiting for longer sequences to finish. If you have more content, maybe think about using a different format like a scrolling gallery or paginated cards that let users control the pace entirely.
Should slideshows autoplay?
Autoplay can work for ambient content like hero banners, but it should always pause on hover and provide visible controls. Never autoplay slideshows with important text content that users need to read. When in doubt, start paused and let users initiate playback.
How do I lazy load slideshow images?
Load the first slide right away for above-the-fold performance. For the rest of the slides you can use loading="lazy" on images, or load them with JavaScript when the slideshow initializes. With Cloudinary, you can also use low-quality placeholders (q_10,e_blur:1000) that load instantly, then swap to full quality when the slide becomes active.