Accessibility

Overview

Splide follows W3C Carousel Concepts to improve accessibility as much as possible. Basically, they recommend that the accessible slider should guarantee these 4 requirements:

  • People using keyboard navigation and voice input software can navigate between individual items.
  • People using screen readers will understand which item is currently shown and how to navigate between items.
  • People who are distracted by movement can pause animations.
  • People who need more time to read can pause animations, providing them with sufficient time to read and understand carousel content.

The Autoplay component already deals with the last two problems. W3C suggests providing play/pause button, but it's up to you.

This page explains how Splide tackles the first two problems.

Keyboard

Keyboard Shortcuts

The user can control the slider by keyboard shortcuts binding these actions:

ShortcutDescription
Go to the previous page in LTR or the next page in RTL
Go to the next page in LTR or the previous page in RTL
Go to the previous page in TTB
Go to the previous page in TTB

By default, Splide listens to the keydown event of the document, which means all slides simultaneously correspond with shortcuts if you have multiple slides in the page. If you are not comfortable about this behaviour, switch the keyboard option to 'focused', and the keyboard can navigate the slider only when it contains the active (focused) element. This option inserts tabindex="0" to the root element since the slider may have no focusable element.

Tab Control

The user can navigate through visible slides, arrows and pagination by the Tab key:

  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09

By setting the slideFocus option to false, you can disable each slide from getting focused, but this does not satisfy the first requirement mentioned earlier.

You need to emphasize the focused element by yourself.

Pagination

This W3C guide also recommends setting focus to the active slide for keyboard and assistive technology users:

When users select an item with those navigation buttons, the focus should be set on the selected item. In this case, the focus needs to be set to the <li> element that has the class current set, after the change or transition. This makes interaction easier for keyboard and assistive technology users.

Although Splide v2 does not have such functionality, I've implemented it for v3. Try clicking the pagination item, and you'll see the focus moves to the active slide:

  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09

Focusable Elements

Splide hides slides which are not completely visible in the viewport by using the aria-hidden attribute on them from screen readers and other assistive technologies. But if they contain focusable elements, such as <input> and <textarea>, keyboard users are still able to reach them by the Tab key.

Lighthouse detects such elements and complains:

Focusable descendents within an [aria-hidden="true"] element prevent those interactive elements from being available to users of assistive technologies like screen readers.

To solve this problem, Splide assigns tabindex="-1" to following focusable elements in hidden slides:

  • <a>
  • <button>
  • <textarea>
  • <input>
  • <select>
  • <iframe>

This approach works well in most cases, but fails if a slide contains these other elements:

  • <area>
  • <audio controls>
  • <summary>
  • <video controls>
  • An element with the contentEditable attribute
  • An element with tabindex="0"

By changing the focusableNodes option (this is a query string), you can handle them other than the tabindex attribute:

{
focusableNodes: 'a, button, ..., area, [contenteditable]',
}
JavaScript

Unfortunately, the option above can not handle tabindex="0", since the index should be restored when the ascendant slide becomes visible. I believe no one controls the tabindex attribute in a slider, but you can manually toggle the index by listening to visible and hidden events.

ARIA Attributes

Every control has appropriate ARIA attributes for assistive technologies. All you have to do is translate texts for your users.

Slides

aria-hidden

true for slides out of the view port.

Arrows

aria-controls

The ID of the track element.

aria-label

"Previous slide" or "Go to last slide" for the previous arrow. "Next slide" or "Go to first slide" for a next arrow.

Also, arrows will have the disabled attribute when there is no slide to go.

Pagination

aria-controls

The ID or IDs of slides.

aria-label

"Go to slide %s" or "Go to page %s".

Play/Pause

aria-controls

The ID of the track element.

aria-label

"Start autoplay" for the play button. "Pause autoplay" for the pause button.

If the isNavigation option is true, the slider behaves as the navigation of another slider. Splide assigns to the menu role to the list element and the menuitem to each slide.

List Element

role

'menu'

Each Slide

role

'menuitem'

aria-controls

The ID or IDs which the slider sync with.

aria-label

"Go to slide %s"

Announcing The Active Slide

All that Splide lacks is announcing the active slide by the WAI-ARIA region. In this page, W3C says:

Use a WAI-ARIA live region to inform screen reader users what item is currently shown.

But also, I know there is dispute that announcing the active slide every time when the slider moves can be annoying, especially when autoplay is active. I've decided not to include this functionality in this version, but I'll show you how to implement it as an extension instead:

export function LiveRegion( Splide ) {
let liveRegion;
function mount() {
liveRegion = document.createElement( 'div' );
liveRegion.setAttribute( 'aria-live', 'polite' );
liveRegion.setAttribute( 'aria-atomic', 'true' );
liveRegion.classList.add( 'visually-hidden' );
Splide.root.appendChild( liveRegion );
Splide.on( 'moved', onMoved );
}
function onMoved() {
liveRegion.textContent = `Slide ${ Splide.index + 1 } of ${ Splide.length }`;
}
function destroy() {
Splide.root.removeChild( liveRegion );
}
return {
mount,
destroy,
}
}
JavaScript

This extension does 2 things:

  • Creates a live region field
  • Updates the text after the slider moves

And then, we need to "visually" hide the text by the well known technique:

.visually-hidden {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
CSS

Finally, initialize Splide with registering the extension:

import { LiveRegion } from '...';
new Splide( '.splide' ).mount( { LiveRegion } );
JavaScript

You can test the result in the following example. Start your screen reader and try moving the slider. It will inform you of the current slider number every time when the transition ends:

  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09