The Easiest Accessible Swiper.JS Carousel Solution to Date


At Graceful Web Studio, we’ve implemented Swiper JS carousels on dozens of websites. Nearly every client—especially those in mission-driven or regulated industries—has asked for a carousel that’s not only interactive and space-saving, but also WCAG-conformant and accessible to keyboard and screen reader users.
Each time, we’ve learned more. And now, with our latest implementation for Walden Mutual Bank, we’ve arrived at what we believe is the simplest, most reliable, and reusable approach to making Swiper JS accessible.
This post details our updated method, explains why we’ve moved beyond Swiper’s built-in accessibility tools, and shows you how to implement an accessible carousel—fast.
Swiper JS includes a module called a11y which claims to:
role="region" and aria-label to the carousel container.swiper-notificationNote: Even when the a11y module is disabled, Swiper JS still automatically adds:
role="group" and aria-label="Slide X of Y" to each .swiper-slidearia-current="true" to the active pagination bulletThis means you don’t need to manually add these ARIA attributes.
However, in practice, here’s what we’ve found:
aria-live is applied to the element (.swiper-wrapper) but again useless with nothing hidden/revealed inside of itFor financial institutions and purpose-driven organizations that need to meet WCAG conformance—not just suggest effort—these gaps are unacceptable.
By adding keyboard: { enabled: true } and pagination: { clickable: true } to our Swiper configuration, we ensured that all navigation controls—including the previous/next arrows and pagination bullets—are fully keyboard accessible. These two options are critical for enabling users to move through the carousel using Tab, Enter, or Spacebar, especially when bypassing the built-in a11y module.
For our client, Walden Mutual Bank, we required:
role="region", aria-label) for screen reader contextWe used Swiper’s core for structure and motion—but completely disregarded its a11y module. Instead, we used there once useless live region (.swiper-notification) for announcements and handled some of the ARIA roles and labels ourselves.
<div class="swiper for-individuals" role="region" aria-label="Tailored solutions for individuals">
<div class="swiper-wrapper">
<div class="swiper-slide"> ... </div>
<div class="swiper-slide"> ... </div>
<div class="swiper-slide"> ... </div>
</div>
<div class="swiper-pagination"></div>
<div class="swiper-button-prev" aria-label="Previous Slide"></div>
<div class="swiper-button-next" aria-label="Next Slide"></div>
</div><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css" />
<style>
:root {
--swiper-navigation-size: 28px;
}
.swiper-button-next,.swiper-button-prev {
top: 35% !important;
width: 30px !important;
height: 30px !important;
color: #000;
}
.swiper-pagination-bullet {
width: 24px; height: 24px;
background-color: #777;
opacity: 1;
}
.swiper-pagination-bullet-active {
background-color: #000;
}
.swiper-pagination-bullet:focus-visible {
outline: -webkit-focus-ring-color auto 1px;
outline-offset: 3px;
}
</style><!-- Swiper CDN -->
<script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script>
<!-- Graceful Web Studio Custom -->
<script>
const swiper = new Swiper('.for-individuals', {
loop: true,
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
pagination: {
el: '.swiper-pagination',
clickable: true,
},
keyboard: {
enabled: true,
onlyInViewport: true,
}
});
// Accessibility announcements
document.addEventListener('DOMContentLoaded', () => {
const wrapper = document.querySelector('.for-individuals .swiper-wrapper');
const liveRegion = document.querySelector('.for-individuals .swiper-notification');
if (wrapper?.hasAttribute('aria-live')) {
wrapper.removeAttribute('aria-live');
}
const announce = (message) => {
if (liveRegion) {
liveRegion.textContent = message;
setTimeout(() => {
liveRegion.textContent = '';
}, 1000);
}
};
document.querySelectorAll('.swiper-pagination-bullet').forEach((btn) => {
btn.addEventListener('click', () => announce('Showing selected slide'));
btn.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
announce('Showing selected slide');
}
});
});
document.querySelector('.swiper-button-next')?.addEventListener('click', () => announce('Showing next slide'));
document.querySelector('.swiper-button-prev')?.addEventListener('click', () => announce('Showing previous slide'));
});
</script>Every website we’ve built recently has asked for some form of accessible carousel. These components are in high demand, and developers are looking for fast, effective ways to implement them without sacrificing usability.
Our goal is to help teams:
If you're building carousels and frustrated with the state of accessibility in libraries like Swiper JS, know this: you are not alone—and you don't have to compromise.
This pattern has been refined with every use, and continues to meet both design and accessibility goals without excess code or confusion.
You can copy the setup above and drop it right into your Webflow project, or reach out to us for a ready-made accessible Swiper component.
Check out Swiper.js demos for more excting styles.
👉 Read our first post: Why I Chose Swiper JS for My Webflow Carousel and How I Made It Accessible
📩 Have questions? DM us or share your feedback on LinkedIn.
Let’s build a more accessible web—one carousel at a time.
We build accessible, conversion-focused Webflow websites for businesses across Yakima and the US. Crystal Scott is a Certified Professional in Web Accessibility (CPWA) with 11+ years of front-end experience.
Our services:
Transparent pricing on every service page. Request a quote and get a response within one business day.
We would love to meet with you face-to-face. Whether virtually or for a coffee. Book a call, and let’s find the right solution for you! We review your site, map goals, and then deliver a clear plan and quote.
