Canonical: https://motionspec.dev/blog/swiper-splide-autoplay-pause-button

Guide

# Swiper and Splide autoplay: the pause button WCAG 2.2.2 asks for

[Kevin Fröba](https://motionspec.dev/about) · 2026-10-09 · MotionSpec

**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](https://motionspec.dev/blog/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](https://www.w3.org/WAI/WCAG22/Understanding/pause-stop-hide.html)

Hover-to-pause is the same pattern with a mouse pointer. The [WAI-ARIA Authoring Practices carousel pattern](https://www.w3.org/WAI/ARIA/apg/patterns/carousel/) 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](https://www.npmjs.com/package/swiper/v/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.

```html
<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>
```

```js
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](https://www.npmjs.com/package/@splidejs/splide/v/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:

```html
<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>
```

```js
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](https://www.w3.org/WAI/WCAG22/Understanding/label-in-name.html) 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](https://motionspec.dev/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](https://motionspec.dev/blog/elementor-animations-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.

**Scope note: this is detection, not a conformance verdict.**

The [free CSS animation accessibility checker](https://motionspec.dev/motion-check) 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](https://motionspec.dev/blog/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.

[The other loop on most agency home pages: the infinite logo marquee that passes WCAG 2.2.2 →](https://motionspec.dev/blog/accessible-infinite-marquee)

MotionSpec checks and compiles UI animation for AI-generated web apps — it does not generate AI video.
