This article is Part Three in our series on building WCAG-conformant SwiperJS carousels in Webflow. If you missed the first two, check them out here:
- Why I Chose Swiper JS for My Webflow Carousel (and How I Made It Accessible)
- The Easiest Accessible Swiper JS Carousel Solution to Date
Today we’re diving into autoplay, one of the trickiest parts of building an accessible carousel. I'll walk you through how to implement an autoplaying testimonial slider that is fully WCAG-conformant using SwiperJS, complete with:
- Pause/play controls (required by WCAG 2.2.2 Pause, Stop, Hide)
- Proper carousel attributes and dynamic announcements (required by WCAG 4.1.2 Name, Role, Value)
Let’s build it step-by-step.
Step 1: Create the Basic Structure in Webflow (No Code Embed Required Yet)
1.1. Add a Collection list from Add panel > Elements > CMS
Give the Collection list wrapper the class: swiper
& swiper-testimonials
Add custom attributes:
role="region"
aria-roledescription="carousel"
aria-labelledby="carousel-testimonial-heading"
(targeting the labeling heading)
1.2. On the nested Collection list
Set the class to swiper-wrapper
1.2. On the nested Collection item
Set the class to swiper-slide
(Swiper will automatically add the required role="group"
and an aria-label="# / #"
)
1.4. Inside each CMS item, add your testimonial content with a rich text element.
In your CMS add the quote in a rich text element so you can use the blockquote
tag.
For demo purposes, use quotes like:
“The service was excellent, and I’d recommend them to anyone.” — Jane, CA
1.5. Swiper.js automatically adds a span
element with the class: swiper-notification
with the attributes aria-live="assertive" aria-atomic="true"
<span class="swiper-notification" aria-live="assertive" aria-atomic="true"></span>
1.6. Add a pagination bullets container:
Create a div
and assign it the class: swiper-pagination
& swiper-pagination-testimonials
Add custom attributes:
role="group"
aria-label="Slide Controls"
Swiper.js will add the pagination buttons for you with the correct attributes like this:
<span class="swiper-pagination-bullet swiper-pagination-bullet-active" tabindex="0" role="button" aria-label="Go to slide 1" aria-current="true"></span>
Step 2: Add the Pause/Play Buttons (Embed Required)
To meet WCAG 2.2 requirements for pause, stop, and hide, we must give users an accessible way to pause the autoplay. These buttons are not optional when using Swiper’s autoplay functionality. This solution uses accessible, ARIA-compliant controls that can be operated with mouse, keyboard, or assistive technology.
Each button is:
- Contained within a
role="group"
for semantic clarity - Given a descriptive name with an
aria-label
such as "Pause Testimonials" or "Play Testimonials" (In case you have multiple pause and play buttons on your page) - Toggled using the
aria-pressed
attribute so screen reader users understand the current state - Styled visually to reflect state: inactive buttons are gray (
#777
), and the active button is black (#181818
), providing a clear 3:1 color contrast ratio to meet minimum accessibility guidelines for non-text UI components
💡 Pro Tip: The visual toggle and aria-pressed
state must always match. We bind both state and styling in the JavaScript for seamless control.
You will need to use a code embed for the play/pause controls:
<!-- Autoplay Controls -->
<div role="group" aria-label="Testimonials Autoplay Controls" class="autoplay-controls">
<button id="pause-button-testimonials" aria-pressed="false" aria-label="Pause Testimonials" class="pause-button-carousel">
<!-- Pause Icon -->
<svg class="pause-icon" width="32" height="32" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path d="M6 5h4v14H6zM14 5h4v14h-4z" fill="currentColor" />
</svg>
</button>
<button id="play-button-testimonials" aria-pressed="true" aria-label="Play Testimonials" class="play-button-carousel">
<!-- Play Icon -->
<svg class="play-icon" width="32" height="32" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path d="M8 5v14l11-7z" fill="currentColor" />
</svg>
</button>
</div>
Step 3: Add and Configure the JavaScript
Add this to your before </body>
section or via a Webflow embed:
This JavaScript does three important things:
- Initializes the Swiper instance with autoplay, keyboard control, and clickable pagination bullets.
- Controls the play/pause state of autoplay using two buttons. Clicking either button updates the Swiper state and toggles the correct
aria-pressed
attribute. At the same time, our CSS ensures that the active button turns dark (#181818
) and the inactive one light gray (#777777
), achieving at least a 3:1 color contrast ratio. This satisfies both visual and screen reader feedback requirements. - Adds event listeners to pagination bullets so that when clicked or activated via keyboard (Enter or Space), the script triggers a live announcement of the slide change using the
.swiper-notification
region. It clears the announcement after 1 second to avoid repetition and clutter.
💡 Pro Tip: Want to confirm your announcements are working as intended? Test with a screen reader:
- Download NVDA for free on Windows
- Explore VoiceOver on Mac
Screen readers will announce the testimonial change when pagination bullets are activated. This is how you verify your implementation is usable for everyone. Swiper sometimes inserts .swiper-notification
outside the expected hierarchy—use fallback logic to find it safely.
If your bullets aren't announcing properly, inspect your DOM. Swiper sometimes inserts .swiper-notification
outside the expected hierarchy—use fallback logic to find it safely.
<script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script>
<script>
document.addEventListener("DOMContentLoaded", () => {
const swiperTestimonialsEl = document.querySelector(".swiper.swiper-testimonials");
if (!swiperTestimonialsEl) return;
const swiperTestimonials = new Swiper(swiperTestimonialsEl, {
autoplay: {
delay: 8000,
disableOnInteraction: false,
},
pagination: {
el: ".swiper-pagination-testimonials",
clickable: true,
},
keyboard: {
enabled: true,
onlyInViewport: true,
},
});
const playButton = document.getElementById("play-button-testimonials");
const pauseButton = document.getElementById("pause-button-testimonials");
if (playButton && pauseButton) {
playButton.addEventListener("click", () => {
swiperTestimonials.autoplay.start();
playButton.setAttribute("aria-pressed", "true");
pauseButton.setAttribute("aria-pressed", "false");
});
pauseButton.addEventListener("click", () => {
swiperTestimonials.autoplay.stop();
playButton.setAttribute("aria-pressed", "false");
pauseButton.setAttribute("aria-pressed", "true");
});
}
const liveRegion = swiperTestimonialsEl.querySelector(".swiper-notification")
|| document.querySelector(".swiper-testimonials ~ .swiper-notification")
|| document.querySelector(".swiper-notification");
const announce = (msg) => {
if (liveRegion) {
liveRegion.textContent = msg;
setTimeout(() => {
liveRegion.textContent = "";
}, 1000);
}
};
document.querySelectorAll(".swiper-pagination-testimonials .swiper-pagination-bullet").forEach((btn) => {
btn.addEventListener("click", () => announce("Showing selected testimonial"));
btn.addEventListener("keydown", (e) => {
if (e.key === "Enter" || e.key === " ") {
announce("Showing selected testimonial");
}
});
});
});
</script>
Step 4: Apply the CSS
This CSS is doing a few key things:
- Core Swiper Styling: It applies basic layout properties to the Swiper container so the carousel remains responsive and centered within its parent.
- Testimonial Slide Design: Adds padding and centers content inside each
.swiper-slide
for clean layout and readability. - Pagination Bullets: These are styled for visibility, with spacing between bullets and color changes for the active state (
#000
) vs. inactive (#777
). We also includefocus-visible
styling to support keyboard users. - Controls Layout: The
.controls-wrapper
and.autoplay-controls
classes ensure that the pause/play buttons are stacked nicely under the bullets and stay visually accessible. - Accessible Button States: Buttons are styled to reflect their current state with color transitions and a visual emphasis when selected via
aria-pressed="true"
. This styling works hand-in-hand with the JavaScript logic to ensure the carousel stays visually clear and compliant.
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css" />
<style>
.swiper {
max-width: 100%;
margin: auto;
}
.swiper.swiper-testimonials {
padding-bottom: 3rem;
}
.swiper-testimonials .swiper-slide {
padding: 1rem;
text-align: center;
}
.swiper-pagination-testimonials {
position: relative;
bottom: 14px !important;
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 0.75rem;
}
.swiper-pagination-testimonials .swiper-pagination-bullet {
width: 24px;
height: 24px;
background-color: #777;
opacity: 1;
border-radius: 50%;
transition: all 0.2s ease;
}
.swiper-pagination-testimonials .swiper-pagination-bullet-active {
background-color: #000;
opacity: 0.9;
}
.swiper-pagination-testimonials .swiper-pagination-bullet:focus-visible {
outline: -webkit-focus-ring-color auto 1px;
outline-offset: 3px;
}
.controls-wrapper {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 0.5rem;
}
.autoplay-controls {
margin-top: 0.5rem;
z-index: 10;
}
.pause-icon,
.play-icon {
width: 32px;
height: 32px;
fill: #181818;
transition: fill 0.2s ease-in-out;
}
.play-button-carousel,
.pause-button-carousel {
color: #777777;
background-color: #F7F7F7;
transition: color 0.2s ease-in-out;
border: none;
padding: 0.25rem;
border-radius: 4px;
}
.play-button-carousel[aria-pressed="true"],
.pause-button-carousel[aria-pressed="true"] {
color: #181818;
}
</style>
Final Thoughts
This solution brings together autoplay, accessibility, and keyboard control in one beautiful Webflow-ready component—but more importantly, it prioritizes people.
Autoplaying content can be overwhelming and even harmful to users with ADHD, motion sensitivity, or vestibular disorders. WCAG requires pause/stop functionality for moving content, and our implementation gives users full control—without sacrificing design.
The combination of aria-pressed
, keyboard operability, and clear visual states (with at least 3:1 contrast between active and inactive buttons) ensures that both screen reader users and sighted keyboard users know exactly what to expect.
💬 If you need help implementing this carousel, or want a fully custom, accessible SwiperJS component for your organization or client site, I'm available for development services on a monthly retainer:👉 View Monthly Retainer Packages
Let’s make every carousel accessible—one component at a time. 🌼