Swiper: Improve Accessibility Features

Created on 15 Jul 2019  Â·  17Comments  Â·  Source: nolimits4web/swiper

This is a (multiple allowed):

  • [ ] bug
  • [x] enhancement
  • [ ] feature-discussion (RFC)
  • Swiper Version: v4.5.0
  • Platform/Target and Browser Versions: EVERY PLATFORM

Expected Behavior

The slider should implement some of the features and html structure of the w3c slider example: https://www.w3.org/TR/wai-aria-practices/examples/carousel/carousel-1/carousel-1.html
The w3c example also has a list of reasons why the features and html structure they used is accessible.
This would make SwiperJS a lot more accessible. Some of the accessibility enhancements can be set with options, but this as the default would help users who don't know how to make it more accessible. It would also be a good reason to use this library instead of other slider libraries, because SwiperJS would be a good option for everyone who wants to apply wcag 2.1 standards. https://www.w3.org/TR/WCAG21/

Some of the features could only be implemented in the accessibility module, for users who don't want to use them.

Actual Behavior

SwiperJS is only partially accessible.

Features to implement:

  • [x] <section> as the outer container, instead of an <div> element
  • [x] outer container has aria-roledescription="carousel"
  • [x] outer container has aria-label="Example Content" (Label can be set like the next/prev slider buttons text)
  • [x] next and previous buttons are <a>-tags
  • [x] next and previous buttons haverole="button"
  • [x] next and previous buttons has aria-controls="swiperID" (this connects with the id of the content)
  • [x] content container has id="swiperID" (this connects with the aria-attribute of the next and previous buttons)
  • [x] content container has aria-live="off" when autoplay is on and aria-live="polite" when autoplay is off
  • [x] content item has role="group"
  • [x] content item has aria-roledescription="slide"
  • [x] content item has the slide number as aria-label. Example:aria-label="1 of 6"
  • [ ] Prevent tabbing to focusable items within out-of-view slides
  • [ ] Make sure disabled controls/buttons are not focusable

Related Issues

The w3c example doesn't use clones, but this issue is also important to improve accessibility: https://github.com/nolimits4web/swiper/issues/2929
Maybe the improved keyboard accessiblity would also fix issues like this: https://github.com/nolimits4web/swiper/issues/2945

a11y feature request help wanted

Most helpful comment

Actually having tabindex="-1" on all focusable elements in invisible slides would be helpful. Currently I use custom code to manage the tabindex myself. I had assumed that a carousel that claims to be accessible would deal with plain links in slides.

For those who are wondering why this is an issue: if you tab through the slides you'll reach at one point a hidden slide. Then they will scroll into view. This breaks the layout and the state managment of Swiper, ie the wrong slides are "active".

All 17 comments

Next and previous should be buttons as they don't take the user to a new page. They also don't need role="button" as the button element has an implied role.

I'm using SiteImprove (an automated a11y review tool) and it's actually penalising me at the moment for the role="button" added by Swiper.

A WAI-ARIA attribute that has the exact same features as the HTML element it has been applied to has been used. The WAI-ARIA attribute is redundant since is doesn't provide the user with any additional information.

For landmarks it has previously been a recommendation to use HTML5 and WAI-ARIA landmark roles together (e.g. WAI-ARIA role="navigation" on HTML5 'nav' elements) to maximize support, but with the widespread adoption of HTML5 this is no longer needed.

The WAI-ARIA attribute can be removed without any impact for end users. The result will be cleaner, easier to maintain code.

I disagree with the above slightly because of this IE11 bug but I think for all modern browsers the role isn't required.

The navigation next and previous buttons have click and keypress events. This causes navigation with a keyboard (pressing enter) to press the button twice.

Having tabindex="-1" on duplicated slides would be very helpful when using loop.

Actually having tabindex="-1" on all focusable elements in invisible slides would be helpful. Currently I use custom code to manage the tabindex myself. I had assumed that a carousel that claims to be accessible would deal with plain links in slides.

For those who are wondering why this is an issue: if you tab through the slides you'll reach at one point a hidden slide. Then they will scroll into view. This breaks the layout and the state managment of Swiper, ie the wrong slides are "active".

BTW aria-roledescription="carousel" is a plain text so it must be translatable if implemented. But aria-roledescription is not welcomed by all a11y advocates.

Speaking of tabindex="-1", it would be nice if disabled navigation buttons would not be focusable.

Sadly SwiperJS is yet another library that has a well-meant a11y-code, but no real interest or knowledge to make it actually work without barriers.

Actually having tabindex="-1" on all focusable elements in invisible slides would be helpful. Currently I use custom code to manage the tabindex myself. I had assumed that a carousel that claims to be accessible would deal with plain links in slides.

For those who are wondering why this is an issue: if you tab through the slides you'll reach at one point a hidden slide. Then they will scroll into view. This breaks the layout and the state managment of Swiper, ie the wrong slides are "active".

Hi masi, would you mind sharing your solution? I also have links in slides and when I tab trough them it breaks the layout. I'm trying to use their API to slideTo the current focused slide but it's not working.

Actually having tabindex="-1" on all focusable elements in invisible slides would be helpful. Currently I use custom code to manage the tabindex myself. I had assumed that a carousel that claims to be accessible would deal with plain links in slides.
For those who are wondering why this is an issue: if you tab through the slides you'll reach at one point a hidden slide. Then they will scroll into view. This breaks the layout and the state managment of Swiper, ie the wrong slides are "active".

Hi masi, would you mind sharing your solution? I also have links in slides and when I tab trough them it breaks the layout. I'm trying to use their API to slideTo the current focused slide but it's not working.

Update: I was able to fix this by changing the scrollLeft property of the swiper container to 0

Actually having tabindex="-1" on all focusable elements in invisible slides would be helpful. Currently I use custom code to manage the tabindex myself. I had assumed that a carousel that claims to be accessible would deal with plain links in slides.

For those who are wondering why this is an issue: if you tab through the slides you'll reach at one point a hidden slide. Then they will scroll into view. This breaks the layout and the state managment of Swiper, ie the wrong slides are "active".

Came to the same solution with tabindex="-1" for invisible slides, which logically make sense as well, since if element is not visible it should not be focusable.

Also, in IE 11 aria-* attributes are missed on navigation buttons.

Here's what I use to add tabindex="-1" to inactive slides:

First I use a polyfill for the inert HTML property:

<script src="https://polyfill.io/v3/polyfill.min.js?features=Element.prototype.inert"></script>

Then I have this function:

const makeAllButCurrentSlideInert = function makeAllButCurrentSlideInert() {
  const currentSlideEl = this.slides[this.realIndex];

  this.slides.each((index, slide) => {
    if (slide !== currentSlideEl) {
      slide.setAttribute("inert", "");
    } else {
      slide.removeAttribute("inert");
    }
  });
};

And I call it every time the carousel updates or is initiated:

const swiperOptions = {
  // …
  on: {
    init() {
      makeAllButCurrentSlideInert.call(this);
    },
    slideChange() {
      makeAllButCurrentSlideInert.call(this);
    },
    slideChangeTransitionEnd() {
      const currentSlideEl = this.slides[this.realIndex];
      currentSlideEl.setAttribute("tabindex", "-1");
      currentSlideEl.focus();
    },
  },
};

The slideChangeTransitionEnd function is a little bonus to set focus to the active slide whenever a transition takes place.

The slideChangeTransitionEnd function is a little bonus to set focus to the active slide whenever a transition takes place.

I also used transitionEnd event, but also listened for keyboard navigation ('keydown'), since I was able to reproduce that issue when using keyboard navigation (arrows) and then moving focus quickly with tab/shift+tab

According to the start post by @fregiese in this issue, the implemented list of new features is part of release 6.3.0.
Please check also the documentation with the new parameters for accessibility in the documentation (https://swiperjs.com/api/#a11y)

  • [x] <section> as the outer container, instead of an <div> element
    --> changed in playground/index.html
  • [x] outer container has aria-roledescription="carousel"
    --> aria-roledescription for outer container can be set by a11y option
  • [x] outer container has aria-label="Example Content" (Label can be set like the next/prev slider buttons text)
    --> aria-label for outer container can be set by a11y option
  • [x] next and previous buttons are <a>-tags
    --> next and previous buttons are <button>-tags in playground/index.html
  • [x] next and previous buttons have role="button"
    --> next and previous buttons get role="button" if no <button>-tags are used
  • [x] next and previous buttons has aria-controls="swiperID" (this connects with the id of the content)
    --> next and previous buttons has aria-controls="[id given for swiper]", see next point
  • [x] content container has id="swiperID" (this connects with the aria-attribute of the next and previous buttons)
    --> content container gets random id if it hasn't an id yet. id corresponds to the aria-attribute of the next and previous buttons
  • [x] content container has aria-live="off" when autoplay is on and aria-live="polite" when autoplay is off
    --> done
  • [x] content item has role="group"
    --> role="group" for content item is set
  • [x] content item has aria-roledescription="slide"
    --> aria-roledescription for content item can be set by a11y option
  • [x] content item has the slide number as aria-label. Example:aria-label="1 of 6"
    --> done (to avoid language problems: 1/6, 2/6 ...)

3834 is the relevant PR for anyone who would like to take a look at the changes

@philwolstenholme @fregiese are there things left to do? or the issue can be closed?

I think there are a few extra tasks:

  • [ ] Prevent tabbing to focusable items within out-of-view slides. I described one way to do that here.
  • [ ] Make sure disabled controls/buttons are not focusable (I'm not sure if this is still an issue, but it was mentioned here: https://github.com/nolimits4web/swiper/issues/3149#issuecomment-650787747)

I updated the inital comment to show the current status

Was this page helpful?
0 / 5 - 0 ratings