Accessible Slider
A dependency-free slider and carousel for vanilla JS, Shopify and WordPress, built to support WCAG 2.1 AA.
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.
Accessible by design, for everyone
Semantic markup, clear focus and real assistive-technology support.
Every control works from the keyboard
Visible focus, a logical order and no keyboard traps.
Color contrast people can actually read
Text and controls that stay legible for low-vision users.
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.
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
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.
| Criterion | How the library handles it | What you still verify |
|---|---|---|
| 1.3.1 Info and RelationshipsLevel A | The 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 A | Arrows, 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 A | Autoplay 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 A | Slides 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 AA | Controls get a 3px :focus-visible outline (default #2563eb, 2px offset). | The contrast of --adaally-focus against your background. |
| 4.1.2 Name, Role, ValueLevel A | Buttons 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 AA | Slide 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 AA | Controls 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.2 | No 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.2 | Partial. 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.
| Key | Action |
|---|---|
| TaborShift+Tab | Move between the slider’s controls. Only the selected dot is in the tab order, so the dots take one stop. |
| EnterorSpace | Activate the focused button: previous, next, a dot, or play/pause. |
| ←Left arrow→Right arrow | On the arrows or play/pause: previous or next slide. |
| ←Left arrow→Right arrow | On a dot: move to the previous or next dot and show that slide. Wraps at the ends. |
| HomeorEnd | First 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.
| Element | Attributes | Why |
|---|---|---|
| Root element | role="region"aria-roledescription="carousel"aria-label | A named region that follows the ARIA Authoring Practices carousel pattern. Falls back to “Carousel” if you give it no name. |
| Each slide | role="group"aria-roledescription="slide"aria-label="N of M" | Tells users which slide they are on and how many there are. |
| Slides not showing | aria-hidden="true"inert | Removes off-screen content from reading and from the tab order. |
| Previous and next | <button>aria-label | Real buttons with names: “Previous slide” and “Next slide”. |
| Play/pause button | aria-pressedaria-label | Lets users stop and restart autoplay (WCAG 2.2.2). The label swaps between start and stop. |
| Dots | role="tablist"role="tab"aria-selectedaria-current="true" | Tablist pattern with a roving tabindex. Each dot is named “Go to slide N”. |
| Live region | aria-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
<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
.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
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.
| Option | Default | Description |
|---|---|---|
autoplay | false | Auto-advance slides. Adds a visible play/pause button. Does not start for users who prefer reduced motion. |
interval | 5000 | Milliseconds between automatic slide changes. |
loop | true | Wrap from the last slide back to the first. |
keyboard | true | Arrow keys, Home and End change slides when focus is on a control. |
pauseOnHover | true | Pause autoplay while the pointer is over the slider. |
pauseOnFocus | true | Pause autoplay while focus is anywhere inside the slider. |
showDots | true | Show dot pagination. |
showArrows | true | Show previous and next buttons. |
slidesPerView | 1 | Slides visible at once, or a responsive map such as { 0: 1, 640: 2, 1024: 3 } (keys are min-width in pixels). |
slidesToScroll | 1 | How 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. |
revealControls | false | Show 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. |
liveRegionClearDelay | 2000 | Milliseconds before the spoken announcement text is cleared. |
labels | see source | Override 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.
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>getrole="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-pressedand 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
<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
npm install @ada-ally/sliderPackage details
- Package
- @ada-ally/slider
- 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.