Swiper and Splide autoplay: the pause button WCAG 2.2.2 asks for
An autoplaying Swiper or Splide slider needs a visible button that stops the rotation — and in Swiper that button has to call autoplay.stop(), not autoplay.pause(). An auto-advancing slider starts on its own, keeps going past five seconds and sits next to other content: the three conditions of WCAG 2.2.2 Pause, Stop, Hide (Level A). Hover-to-pause does not close it. This guide gives the button for both libraries, the reduced-motion setting each one needs, and a trap in Swiper's pause method that we found in its source and reproduced in a browser.
What does WCAG 2.2.2 ask of an autoplaying slider?
A mechanism the user can operate to pause, stop or hide the rotation. The W3C's Understanding document rules out the most common shortcut in one sentence:
"Having an animation stop only so long as a user has focus on it (where it restarts as soon as the user moves the focus away) would not be considered a "mechanism for the user to pause" because it makes the page unusable in the process and would not meet this SC."
— W3C, Understanding Success Criterion 2.2.2
Hover-to-pause is the same pattern with a mouse pointer. The WAI-ARIA Authoring Practices carousel pattern lists what an auto-rotating carousel needs instead: a button for stopping and restarting rotation; rotation that stops when keyboard focus enters the carousel and "does not restart unless the user explicitly requests it to do so"; and rotation that stops while the mouse hovers over it. Pausing on hover is one item on that list, not a substitute for the button.
Why does Swiper's autoplay.pause() not work as a pause button?
Because pause() is built to be temporary. We read the autoplay module of Swiper 14.3.0, the current release on npm on 2026-10-09. Three code paths call resume() on a paused slider: the page becoming visible again after a tab switch, the pointer leaving the slider when pauseOnMouseEnter is on, and the end of a slide transition while waitForTransition is at its default of true. Each of them only checks whether autoplay is still running — and pause() leaves it running. stop() sets swiper.autoplay.running to false, and all three paths return early.
We reproduced both cases in Chromium against Swiper 14.3.0. A slider paused with pause() started rotating again after a simulated tab switch and, with pauseOnMouseEnter on, after the pointer left it. The same slider stopped with stop() stayed still through both. Elementor's carousel widgets, which are built on Swiper, also call stop() and start() in their hover handler.
How do you add a pause button to Swiper?
One button before the slides, stop() and start() behind it, a label that says what the button will do, and a reduced-motion branch that leaves autoplay off until someone asks for it. Swiper 14.3.0 has no prefers-reduced-motion handling of its own — the string does not occur anywhere in the package — so that branch is yours to write.
<section class="hero-carousel" aria-roledescription="carousel" aria-label="Customer stories">
<button type="button" class="carousel-toggle">Stop slide rotation</button>
<div class="swiper">
<div class="swiper-wrapper">
<div class="swiper-slide">…</div>
<div class="swiper-slide">…</div>
</div>
</div>
</section>
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const swiper = new Swiper('.hero-carousel .swiper', {
loop: true,
speed: reduceMotion ? 0 : 600,
autoplay: { delay: 6000, enabled: !reduceMotion },
});
const toggle = document.querySelector('.hero-carousel .carousel-toggle');
function updateToggle() {
toggle.textContent = swiper.autoplay.running ? 'Stop slide rotation' : 'Start slide rotation';
}
toggle.addEventListener('click', () => {
if (swiper.autoplay.running) {
swiper.autoplay.stop(); // not pause(): pause() resumes on its own
} else {
swiper.autoplay.start();
}
updateToggle();
});
// Keyboard focus on a slide stops rotation; only the button restarts it.
swiper.el.addEventListener('focusin', () => {
swiper.autoplay.stop();
updateToggle();
});
updateToggle();
Three details carry the weight. The button is the first thing inside the carousel, so a keyboard user reaches it before the slides. Its label changes with the state — "Stop slide rotation" and "Start slide rotation" are the examples the carousel pattern itself gives — so it needs no aria-pressed. And enabled: !reduceMotion keeps the delay in the configuration: a visitor with Reduce Motion on gets a still slider, can start it from the button, and then sees the normal six-second rhythm, with speed: 0 turning each slide movement into a cut.
We ran this code in Chromium against Swiper 14.3.0 with both motion settings. Without a preference it rotated, the button stopped it, and it stayed stopped through a tab switch and a pointer leave; focusing a link inside a slide stopped it too. With Reduce Motion on it did not start, and the button started it at the configured delay.
How do you add a pause button to Splide?
Splide ships the button; you only have to put it in the markup. In Splide 4.1.4 the autoplay component looks for an element with the class splide__toggle inside the slider, wires it to play and pause, points its aria-controls at the track, and switches its aria-label between "Start autoplay" and "Pause autoplay". The core stylesheet shows whichever of the two spans matches the state:
<section class="splide" aria-label="Customer stories">
<button class="splide__toggle" type="button">
<span class="splide__toggle__play">Start autoplay</span>
<span class="splide__toggle__pause">Pause autoplay</span>
</button>
<div class="splide__track">
<ul class="splide__list">
<li class="splide__slide">…</li>
<li class="splide__slide">…</li>
</ul>
</div>
</section>
new Splide('.splide', {
type: 'loop',
autoplay: true,
interval: 6000,
}).mount();
Keep the visible text identical to the labels Splide sets. The library overwrites the button's accessible name with its i18n.play and i18n.pause strings, and WCAG 2.5.3 Label in Name asks that the name contain the text presented visually — a button that shows "Play" and announces "Start autoplay" fails that. If you translate the labels, translate both through the i18n option.
Splide's defaults cover the rest. pauseOnHover and pauseOnFocus are on, and the reducedMotion default — { speed: 0, rewindSpeed: 0, autoplay: 'pause' } — gives a visitor with Reduce Motion on a slider that does not start by itself and cuts between slides. A rotation stopped with the toggle stays stopped: hover and focus only pause a slider the user has not stopped. We checked each of these in Chromium against 4.1.4. Without the toggle element, though, the hover and focus pauses are all there is, and both end the moment the pointer or focus leaves — the pattern the Understanding document rules out.
What does the free check see on a slider page?
The stylesheet, not the rotation. Slider autoplay is JavaScript: a timer that moves the track. We ran the engine behind the free motion check offline on a test page that loads Swiper 14.3.0's bundle stylesheet and starts an autoplaying Swiper. It listed the library's own transition rules as motion without a reduced-motion guard (the WCAG 2.3.3 lane), put the swiper-preloader-spin keyframes on the list of loading indicators for manual review, and named the rest plainly: "Runtime motion library detected (Swiper) — not audited (V2)." The same page with Splide 4.1.4 got one pagination transition, the splide-loading spinner for review, and the same note. Neither report can tell you whether the slider has a working stop button. That check is yours.
What this does not cover
- Whether the slider should rotate at all. The cleanest way out of 2.2.2 is a slider that waits for the user. If the slides carry text people need to read, rotation works against them whatever the controls.
- How slide changes are announced. Both libraries manage a live region in their own way. Whether a screen reader user gets sensible announcements is a test with a screen reader, not a code read.
- Other sliders. Slick, Flickity, theme sliders and page-builder widgets each have their own API. The test is the same everywhere: start the page, try to stop the rotation with the keyboard alone, switch tabs, come back. For Elementor's widgets, see Elementor and prefers-reduced-motion.
- Later versions. Everything above was read and run on Swiper 14.3.0 and Splide 4.1.4. Re-run the tab-switch test after a major upgrade.
The free CSS animation accessibility checker reads a page's linked CSS files and inline style blocks and reports the failing patterns it can see, each with a fix. It does not certify a page, and it is not a legal determination. For the full manual process across a page's motion — criteria, patterns and checks in order — see how to verify web motion accessibility.
FAQ
Is pause on hover enough for WCAG 2.2.2?
No. A pause that lasts only while the pointer is over the slider ends as soon as it leaves, and keyboard and touch users never trigger it. The W3C Understanding document for 2.2.2 says that an animation which stops only while it has focus, and restarts when focus moves away, is not a mechanism for the user to pause. Pausing on hover is a useful extra next to a stop button, not a replacement for it.
Should a Swiper pause button call autoplay.pause() or autoplay.stop()?
stop(). In Swiper 14.3.0, pause() leaves autoplay running, and the slider resumes on its own after a tab switch, when the pointer leaves with pauseOnMouseEnter on, or at the end of a slide transition. stop() sets autoplay to not running, so none of those paths restart it; start() is the matching call for the button’s second state.
Does Splide respect prefers-reduced-motion?
Yes, by default. In Splide 4 the reducedMotion option defaults to speed 0, rewindSpeed 0 and autoplay 'pause', so with Reduce Motion on the slider does not start rotating and changes slides without a transition. The visitor can still start the rotation from the splide__toggle button if the markup includes one.
Does Swiper respect prefers-reduced-motion?
Not on its own. Swiper 14.3.0 contains no prefers-reduced-motion handling, so the slider autoplays and slides at its normal speed for every visitor unless your code checks the preference, for example with window.matchMedia, and sets autoplay and speed accordingly.
Paste a URL into the free motion check — a static scan against WCAG 2.2.2 and 2.3.3, every finding with its fix. No signup, nothing stored.
Check a page for freeOne page tells you. A dated write‑up is what you hand to a client or a boss: the $29 Motion Report cites every candidate to WCAG 2.2.2 / 2.3.3 and ranks your score against 196 AI‑built pages — read a real one first. Fourteen‑day refund, no questions asked.
The other loop on most agency home pages: the infinite logo marquee that passes WCAG 2.2.2 →
MotionSpec checks and compiles UI animation for AI-generated web apps — it does not generate AI video.
Agency or studio shipping client sites? The Agency plan keeps up to ten client domains under the same keyed WCAG 2.2.2 / 2.3.3 checks, with a dated report per domain — $249/month, cancel any time, first month refunded if the first pass finds nothing nameable.