Status: BetaBuilt to support WCAG 2.1 AA

Accessible Slider

A dependency-free slider and carousel for vanilla JS, Shopify and WordPress, built to support WCAG 2.1 AA.

On this page

Live demo

Try each demo with only your keyboard: Tab to the controls, then use the arrow keys. These demos run the published @ada-ally/slider package. Only demo 3 autoplays, and it has a pause button.

A stylized figure with arms outstretched inside concentric rings, a symbol of access for everyone.

Accessible by design, for everyone

Semantic markup, clear focus and real assistive-technology support.

Three keyboard keys labeled Tab, Enter and Space. The Tab key is highlighted and outlined with a focus ring.

Every control works from the keyboard

Visible focus, a logical order and no keyboard traps.

Two sample tiles showing the letters Aa, dark on white and white on dark, above the text 4.5 to 1 minimum contrast.

Color contrast people can actually read

Text and controls that stay legible for low-vision users.

Demo 1 · Image hero. Full-width slides with a text overlay, previous/next buttons and dot pagination.

WCAG 1.1.1

Text alternativesLevel A

Describe images that carry meaning.

WCAG 1.4.3

Contrast minimumLevel AA

Text needs at least 4.5:1 against its background.

WCAG 2.1.1

KeyboardLevel A

Everything works without a mouse.

WCAG 2.4.7

Focus visibleLevel AA

Show where keyboard focus is.

WCAG 1.4.4

Resize textLevel AA

Text scales to 200% without loss.

WCAG 3.3.1

Error identificationLevel A

Say what went wrong, in text.

Demo 2 · Topic cards. Several slides visible at once (3, 2 or 1 depending on width) with previous/next buttons and page dots.

Accessibility tip 1 of 3

Every interactive element should work with a keyboard alone.

Keyboard access · WCAG 2.1.1

Accessibility tip 2 of 3

Write alt text that says what an image is for, not just what it shows.

Text alternatives · WCAG 1.1.1

Accessibility tip 3 of 3

Never rely on color alone to show an error, a status or a required field.

Use of color · WCAG 1.4.1

Demo 3 · Text slides. Text-only content with a fade transition and autoplay (7-second interval). The pause button stays visible, and autoplay stops on hover and while focus is inside.

Which WCAG criteria does Accessible Slider support?

The slider is built to support WCAG 2.1 AA for the structure and controls it provides: keyboard operation, a pause control for autoplay, status messages and visible focus. Your slide content, colors and page layout still need their own checks.

WCAG success criteria: library coverage and developer checks
CriterionHow the library handles itWhat you still verify
1.3.1 Info and RelationshipsLevel AThe carousel is a labeled region. Each slide is a group labeled “N of M”. The dots are a tablist.Heading and list structure inside your slides. Use <div> or <li> for slides, not <article> (see Known issues).
2.1.1 KeyboardLevel AArrows, dots and play/pause are real buttons. Arrow keys, Home and End work when focus is on a control.Interactive content inside your slides, and that nothing else on the page traps focus.
2.2.2 Pause, Stop, HideLevel AAutoplay is off by default. When on, a visible play/pause button is added; autoplay pauses on hover and while focus is inside, and never starts for users who prefer reduced motion.Choose an interval long enough to read (default 5 seconds) and keep the button visible.
2.4.3 Focus OrderLevel ASlides that are not showing are inert and aria-hidden, so Tab never lands on hidden content.The reading and focus order inside each slide.
2.4.7 Focus VisibleLevel AAControls get a 3px :focus-visible outline (default #2563eb, 2px offset).The contrast of --adaally-focus against your background.
4.1.2 Name, Role, ValueLevel AButtons have names (“Previous slide”, “Next slide”). Play/pause exposes aria-pressed. Dots expose role="tab" and aria-selected.Translated labels (the labels option) and a unique name for each slider (the label option).
4.1.3 Status MessagesLevel AASlide changes are announced through a shared polite live region, without moving focus.That your theme does not hide the live region. See Troubleshooting in the README.
1.4.11 Non-text ContrastLevel AAControls use currentColor and CSS variables you can set.A 3:1 contrast for arrows, dots and outlines against your design.
2.5.7 Dragging MovementsLevel AA · WCAG 2.2No drag or swipe gestures are implemented. Every movement is a button press.Nothing, unless you add your own gestures.
2.5.8 Target Size (Minimum)Level AA · WCAG 2.2Partial. Arrow buttons are 40px by default. Dots are 12px, and “line” dots are 3px tall.Enlarge the dots with --adaally-dot-size (24px or more) or rely on the arrow buttons. See Known issues.

How do keyboard users operate the slider?

Tab moves through the arrows, dots and play/pause button. With focus on a control, the arrow keys, Home and End change slides. Arrow keys inside your slide content keep their normal behavior.

Keyboard interaction
KeyAction
TaborShift+TabMove between the slider’s controls. Only the selected dot is in the tab order, so the dots take one stop.
EnterorSpaceActivate the focused button: previous, next, a dot, or play/pause.
Left arrowRight arrowOn the arrows or play/pause: previous or next slide.
Left arrowRight arrowOn a dot: move to the previous or next dot and show that slide. Wraps at the ends.
HomeorEndFirst slide, or the last position. With several slides showing, End goes to the last full set.

What do screen reader users hear?

A screen reader announces the slider as a named carousel and each slide as “N of M”. When a slide changes, its text is spoken through a polite live region, without moving focus.

  • Changes caused by autoplay are announced only while keyboard focus is inside the carousel. Clicks, keys and dots always announce.
  • If several slides move at once, a short count is announced instead, such as “Showing 2 new slides”.
  • Add data-adaally-announce="…" to a slide to control exactly what is spoken for it.
  • Slides that are not showing are hidden from screen readers and from the tab order.

ARIA implementation

The library sets these attributes when it initializes. A role or aria-label you set yourself on the element is kept.

ARIA roles and attributes
ElementAttributesWhy
Root elementrole="region"aria-roledescription="carousel"aria-labelA named region that follows the ARIA Authoring Practices carousel pattern. Falls back to “Carousel” if you give it no name.
Each sliderole="group"aria-roledescription="slide"aria-label="N of M"Tells users which slide they are on and how many there are.
Slides not showingaria-hidden="true"inertRemoves off-screen content from reading and from the tab order.
Previous and next<button>aria-labelReal buttons with names: “Previous slide” and “Next slide”.
Play/pause buttonaria-pressedaria-labelLets users stop and restart autoplay (WCAG 2.2.2). The label swaps between start and stop.
Dotsrole="tablist"role="tab"aria-selectedaria-current="true"Tablist pattern with a roving tabindex. Each dot is named “Go to slide N”.
Live regionaria-live="polite"aria-atomic="true"Announces slide changes. One region is shared by every slider on the page.

Implementation

Add the markup, load the stylesheet and script, and the slider starts itself. Use plain <div> elements for the slides.

HTML

index.html
<div data-adaally-slider='{"label": "Featured stories"}'>
  <div>
    <h3>First slide</h3>
    <p>Slide content goes here.</p>
  </div>
  <div>
    <h3>Second slide</h3>
    <p>Slide content goes here.</p>
  </div>
  <div>
    <h3>Third slide</h3>
    <p>Slide content goes here.</p>
  </div>
</div>

CSS

styles.css
.adaally-slider {
  --adaally-color: #1a1a1a;     /* text and control color */
  --adaally-bg: #ffffff;        /* control background */
  --adaally-focus: #4e4d98;     /* focus outline color */
  --adaally-btn-size: 2.5rem;   /* arrow and play buttons */
  --adaally-dot-size: 1.5rem;   /* 24px dots meet the WCAG 2.2 target size */
  --adaally-gap: 1rem;          /* space between slides */
}

JavaScript

main.js
import AdaallySlider from '@ada-ally/slider';
import '@ada-ally/slider/css';

const slider = new AdaallySlider('#hero', {
  label: 'Featured stories',
  slidesPerView: { 0: 1, 640: 2, 1024: 3 },
});

document.querySelector('#hero').addEventListener('adaally:change', (e) => {
  console.log(`Slid to ${e.detail.index}`);
});

Options

Pass options as JSON in the data-adaally-slider attribute, or as the second argument to new AdaallySlider(). Every option is optional.

Configuration options
OptionDefaultDescription
autoplayfalseAuto-advance slides. Adds a visible play/pause button. Does not start for users who prefer reduced motion.
interval5000Milliseconds between automatic slide changes.
looptrueWrap from the last slide back to the first.
keyboardtrueArrow keys, Home and End change slides when focus is on a control.
pauseOnHovertruePause autoplay while the pointer is over the slider.
pauseOnFocustruePause autoplay while focus is anywhere inside the slider.
showDotstrueShow dot pagination.
showArrowstrueShow previous and next buttons.
slidesPerView1Slides visible at once, or a responsive map such as { 0: 1, 640: 2, 1024: 3 } (keys are min-width in pixels).
slidesToScroll1How many slides Next and Previous advance by.
transition'slide''slide' moves a flex track. 'fade' cross-fades stacked slides.
label—The carousel’s accessible name (aria-label). Falls back to an existing label, then to “Carousel”.
icons{}Custom icon markup: { prev, next, play, pause }. Pass inline SVG.
revealControlsfalseShow arrows and play/pause only on hover or focus-within. Dots stay visible. Always shown on touch.
playButtonPosition'top-left'Where the play/pause button sits: top-left, top-center, top-right, bottom-left, bottom-center or bottom-right.
dotsShape'dot''dot' for circles, 'line' for thin bars.
dotsPosition'overlay''overlay' floats dots over the slide. 'below' puts them under the slider.
liveRegionClearDelay2000Milliseconds before the spoken announcement text is cleared.
labelssee sourceOverride any built-in label for translation, including slide(i, total) and the announcement functions.

What accessibility issues come up most often in carousels?

The most common carousel problems are autoplay with no way to stop it, controls with no accessible name, off-screen slides that keyboard users can still reach, and slide changes that screen readers never hear.

Autoplay with no pause control

Wrong

<div class="carousel" data-autoplay="3000">
  <!-- moves every 3 seconds, no way to stop it -->
</div>

Right

<div data-adaally-slider='{"autoplay": true, "interval": 5000}' aria-label="Featured">
  <!-- a play/pause button is added for you -->
</div>

Related: WCAG 2.2.2 Pause, Stop, Hide

Controls with no accessible name

Wrong

<button class="next">›</button>

Right

<button type="button" aria-label="Next slide">›</button>

Related: WCAG 4.1.2 Name, Role, Value

Off-screen slides still reachable by keyboard

Wrong

<div class="slide" style="transform: translateX(100%)">
  <a href="/offer">Offer</a>
</div>

Right

<div class="slide" aria-hidden="true" inert>
  <a href="/offer">Offer</a>
</div>

Related: WCAG 2.4.3 Focus Order

Slide changes that nobody hears

Wrong

<!-- only a CSS class changes when the slide changes -->
<div class="slide active">…</div>

Right

<div class="visually-hidden" aria-live="polite" aria-atomic="true">
  <!-- set its text to "Slide 2 of 5" on change -->
</div>

Related: WCAG 4.1.3 Status Messages

Controls that only appear on mouse hover

Wrong

.controls { opacity: 0; }
.carousel:hover .controls { opacity: 1; }

Right

<div data-adaally-slider='{"revealControls": true}' aria-label="Featured">
  <!-- controls appear on hover or focus, and are always visible on touch -->
</div>

Related: WCAG 2.4.7 Focus Visible

Pagination dots too small to hit

Wrong

.dot { width: 8px; height: 8px; }

Right

.dot { width: 24px; height: 24px; }

Related: WCAG 2.5.8 Target Size (Minimum)

Seeing these on your own site?

Most of them show up within minutes of a keyboard and screen reader pass. Here’s the checklist our team follows when we review a page.

See how we audit

Limitations and developer responsibilities

  • Slide content, colors and the surrounding page are your responsibility. WCAG conformance applies to whole pages, not to a single component.
  • The WCAG 2.2 target size criterion (2.5.8) is not met by the default dots. See Known issues.
  • There is no swipe or drag gesture on touch screens. Arrows and dots work on touch.
  • Screen reader results are not published yet.
  • Auto-init runs once, on page load. For content added later (the Shopify theme editor, popups, tabs), call Adaally.autoInit() on it.
  • In WordPress and Elementor, add the markup with an HTML block or widget. Curly quotes in the JSON options are ignored silently.
  • This library is built to support WCAG 2.1 AA. Conformance depends on your full page and content.

Known issues

  • Slides written as <article> get role="group", which ARIA does not allow on that element. Use <div> or <li> for slides.
  • Dots are below the WCAG 2.2 minimum target size (24×24px) by default: 12px circles, and 3px-tall bars in the “line” style. Enlarge them with --adaally-dot-size, or rely on the arrow buttons.
  • The play/pause button sets both aria-pressed and a changing label. The ARIA Authoring Practices advise using only one, because some screen readers announce both.

Installation

Shopify and WordPress sites usually load the two files from a CDN or upload them to the theme. Bundler projects can install from npm. The source code is on GitHub.

CDN

HTML
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ada-ally/slider@0.1/dist/adaally-slider.css">
<script src="https://cdn.jsdelivr.net/npm/@ada-ally/slider@0.1/dist/adaally-slider.umd.js" defer></script>

npm

Shell
npm install @ada-ally/slider

Package details

Version
0.1.0
License
MIT
Size
~6 KB min+gzip
Dependencies
None
Last reviewed

Want a second pair of eyes on your whole site?

A single component is one piece. If you’d like help with the rest, our team audits and fixes sites with real assistive-technology testing. No overlays.