The W3C Accessibility Working Group started the Web Accessibility Initiative to assist developers with building accessibility semantics into web patterns and widgets. Widgets are emphasized in the creation of accessible experiences. Design Systems like Material Design, Fluent UI, and Human Interface UI, are Widget-Based Design Systems that are built with Web Components on the Browser platform and they closely follow the Web Accessibility Guidelines when translated to Native platforms. Using these semantics will not only improve the accessibility of your websites but also your development speed.
Have you ever had trouble building and Image Carousel or Alert Dialogue to the point that you downloaded a package from NPM to do it? Not anymore. The Web Accessibility initiative has your back. They have sample widgets and patterns that you can view with CodePen. Downloads the sample into your project, modify it, and you're done. You will know how to build an Image Carousel and it will be accessible, without downloading a package from NPM
Example Image Carousel:
- HTML
<section id="myCarousel" class="carousel" aria-roledescription="carousel" aria-label="Highlighted television shows">
<div class="carousel-inner">
<div class="controls">
<button type="button" class="rotation pause" aria-label="Stop automatic slide show">
<svg width="42" height="34" version="1.1" xmlns="http://www.w3.org/2000/svg">
<rect class="background" x="2" y="2" rx="5" ry="5" width="38" height="24"></rect>
<rect class="border" x="4" y="4" rx="5" ry="5" width="34" height="20"></rect>
<polygon class="pause" points="17 8 17 20"></polygon>
<polygon class="pause" points="24 8 24 20"></polygon>
<polygon class="play" points="15 8 15 20 27 14"></polygon>
</svg>
</button>
<button type="button" class="previous" aria-controls="myCarousel-items" aria-label="Previous Slide">
<svg width="42" height="34" version="1.1" xmlns="http://www.w3.org/2000/svg">
<rect class="background" x="2" y="2" rx="5" ry="5" width="38" height="24"></rect>
<rect class="border" x="4" y="4" rx="5" ry="5" width="34" height="20"></rect>
<polygon points="9 14 21 8 21 11 33 11 33 17 21 17 21 20"></polygon>
</svg>
</button>
<button type="button" class="next" aria-controls="myCarousel-items" aria-label="Next Slide">
<svg width="42" height="34" version="1.1" xmlns="http://www.w3.org/2000/svg">
<rect class="background" x="2" y="2" rx="5" ry="5" width="38" height="24"></rect>
<rect class="border" x="4" y="4" rx="5" ry="5" width="34" height="20"></rect>
<polygon points="9 11 21 11 21 8 33 14 21 20 21 17 9 17"></polygon>
</svg>
</button>
</div>
<div id="myCarousel-items" class="carousel-items" aria-live="off">
<div class="carousel-item active" role="group" aria-roledescription="slide" aria-label="1 of 6">
<div class="carousel-image">
<a href="#">
<img src="../../../../../../content-images/wai-aria-practices/patterns/carousel/examples/images/amsterdamslide__800x600.jpg" alt="Walking Tour in Amsterdam">
</a>
</div>
<div class="carousel-caption">
<h3>
<a href="#" id="carousel-label-1"> Dynamic Europe: Amsterdam, Prague, Berlin </a>
</h3>
<div class="hidden-xs hidden-sm">
<p><span class="contrast">7 pm Tuesday, March 3, on TV</span></p>
</div>
</div>
</div>
<div class="carousel-item" role="group" aria-roledescription="slide" aria-label="2 of 6">
<div class="carousel-image">
<a href="#">
<img src="../../../../../../content-images/wai-aria-practices/patterns/carousel/examples/images/lands-endslide__800x600.jpg" alt="Land's End in Cornwall">
</a>
</div>
<div class="carousel-caption">
<h3>
<a href="#" id="carousel-label-2"> Travel to Southwest England and Paris </a>
</h3>
<div>
<p><span class="contrast">Sept. 14 to Sept. 24 or 27</span></p>
</div>
</div>
</div>
<div class="carousel-item" role="group" aria-roledescription="slide" aria-label="3 of 6">
<div class="carousel-image">
<a href="#">
<img src="../../../../../../content-images/wai-aria-practices/patterns/carousel/examples/images/trustslide-2__800x600.jpg" alt="Mom and daughter play Daniel Tiger game on notebook computer.">
</a>
</div>
<div class="carousel-caption">
<h3>
<a href="#" id="carousel-label-3"> Great Children's Programming on Public TV </a>
</h3>
<div></div>
</div>
</div>
<div class="carousel-item" role="group" aria-roledescription="slide" aria-label="4 of 6">
<div class="carousel-image">
<a href="#">
<img src="../../../../../../content-images/wai-aria-practices/patterns/carousel/examples/images/foyleswarslide__800x600.jpg" alt="A man in a suit and fedora and a woman with coiffed hair look sternly into the camera.">
</a>
</div>
<div class="carousel-caption">
<h3>
<a href="#" id="carousel-label-4"> Foyle’s War Revisited </a>
</h3>
<div>
<p><span class="contrast">8 pm Sunday, March 8, on TV: Sneak peek at the final season.</span></p>
</div>
</div>
</div>
<div class="carousel-item" role="group" aria-roledescription="slide" aria-label="5 of 6">
<div class="carousel-image">
<a href="#">
<img src="../../../../../../content-images/wai-aria-practices/patterns/carousel/examples/images/britcomdavidslide__800x600.jpg" alt="British flag with WILL-TV host David Thiel.">
</a>
</div>
<div class="carousel-caption">
<h3>
<a href="#" id="carousel-label-5"> Great Britain Vote: 7 pm Sat. </a>
</h3>
<div></div>
</div>
</div>
<div class="carousel-item" role="group" aria-roledescription="slide" aria-label="6 of 6">
<div class="carousel-image">
<a href="#">
<img src="../../../../../../content-images/wai-aria-practices/patterns/carousel/examples/images/mag800-2__800x600.jpg" alt="Mid-American Gardener panelists on the set">
</a>
</div>
<div class="carousel-caption">
<h3>
<a href="#" id="carousel-label-6"> Mid-American Gardener: Thursdays at 7 pm </a>
</h3>
<div class="hidden-xs hidden-sm">
<p><span class="contrast">Watch the latest episodes.</span></p>
</div>
</div>
</div>
</div>
</div>
</section>
<div class="col-sm-1"></div>
- CSS
/* .carousel */
img.reload {
padding: 0.25em;
display: block-inline;
position: relative;
top: 6px;
height: 0.9em;
}
.carousel {
background-color: #eee;
max-width: 900px;
}
.carousel .carousel-inner {
position: relative;
}
.carousel .carousel-items {
padding: 5px;
}
.carousel .carousel-items.focus {
padding: 2px;
border: solid 3px #005a9c;
}
.carousel .carousel-item {
display: none;
max-height: 400px;
max-width: 900px;
position: relative;
overflow: hidden;
width: 100%;
}
.carousel .carousel-item.active {
display: block;
}
/* More like bootstrap, less accessible */
.carousel .carousel-item .carousel-image a img {
height: 100%;
width: 100%;
}
.carousel .carousel-item .carousel-caption a {
cursor: pointer;
text-decoration: underline;
color: #fff;
font-weight: 600;
}
.carousel .carousel-item .carousel-caption a,
.carousel .carousel-item .carousel-caption span.contrast {
display: inline-block;
margin: 0;
padding: 6px;
background-color: rgb(0 0 0 / 65%);
border-radius: 5px;
border: 0 solid transparent;
}
.carousel-moreaccessible .carousel-items .carousel-image a {
display: block;
margin: 0;
padding: 5px;
text-decoration: none;
border: none;
}
.carousel-moreaccessible .carousel-item .carousel-caption a {
display: inline-block;
margin: 0;
padding: 6px;
color: black;
background-color: transparent;
border: none;
border-radius: 5px;
}
.carousel-moreaccessible .carousel-item .carousel-caption span.contrast,
.carousel-moreaccessible .carousel-item .carousel-caption span.contrast:hover {
background-color: transparent;
}
.carousel .carousel-item .carousel-caption a:hover,
.carousel .carousel-item .carousel-caption span.contrast:hover {
background-color: rgb(0 0 0 / 100%);
}
.carousel .carousel-item .carousel-caption a:focus {
padding: 4px;
border: 2px solid #fff;
background-color: rgb(0 0 0 / 100%);
outline: none;
border-width: 2px solid #fff;
color: #fff;
}
.carousel .carousel-item .carousel-caption p {
font-size: 1em;
line-height: 1.5;
margin-bottom: 0;
}
.carousel .carousel-item .carousel-caption {
position: absolute;
right: 15%;
bottom: 0;
left: 15%;
padding-top: 20px;
padding-bottom: 20px;
color: #fff;
text-align: center;
}
/* Shared CSS for Pause, Previous and Next Buttons */
.carousel .controls {
box-sizing: border-box;
position: absolute;
top: 1em;
z-index: 10;
display: flex;
width: 100%;
padding: 0.25em 1.25em 0;
}
.carousel .controls button {
position: absolute;
z-index: 10;
flex: 0 0 auto;
margin: 0;
padding: 0;
border: none;
background: transparent;
outline: none;
}
.carousel .controls button.previous {
right: 70px;
}
.carousel .controls button.next {
right: 18px;
}
/* SVG Controls */
.carousel .controls svg .background {
stroke: black;
fill: black;
stroke-width: 1px;
opacity: 0.6;
}
.carousel .controls svg .border {
fill: transparent;
stroke: transparent;
stroke-width: 2px;
}
.carousel .controls svg .pause {
stroke-width: 4;
fill: transparent;
stroke: transparent;
}
.carousel .controls svg .play {
stroke-width: 1;
fill: transparent;
stroke: transparent;
}
.carousel .controls .pause svg .pause {
fill: white;
stroke: white;
}
.carousel .controls .play svg .play {
fill: white;
stroke: white;
}
.carousel .controls svg polygon {
fill: white;
stroke: white;
}
.carousel .controls button:focus svg .background,
.carousel .controls button:hover svg .background,
.carousel .controls button:hover svg .border {
fill: #005a9c;
stroke: #005a9c;
opacity: 1;
}
.carousel .controls button:focus svg .border {
stroke: white;
}
/* More accessible carousel styles, with caption and controls above/below image */
.carousel-moreaccessible {
padding: 0;
margin: 0;
position: relative;
border: #eee solid 4px;
border-radius: 5px;
}
/* Shared CSS for Pause and Tab Controls */
.carousel-moreaccessible .controls {
position: relative;
top: 0;
left: 0;
padding: 0.25em 0.25em 0;
}
.carousel.carousel-moreaccessible .controls {
position: static;
height: 36px;
}
.carousel.carousel-moreaccessible .controls button.previous {
right: 60px;
}
.carousel.carousel-moreaccessible .controls button.next {
right: 6px;
}
.carousel-moreaccessible .carousel-items,
.carousel-moreaccessible .carousel-items.focus {
padding: 0;
border: none;
}
.carousel-moreaccessible .carousel-items.focus .carousel-image a {
padding: 2px;
border: 3px solid #005a9c;
}
/* More accessible caption styling */
.carousel-moreaccessible .carousel-item {
padding: 0;
margin: 0;
max-height: none;
}
.carousel-moreaccessible .carousel-item .carousel-caption {
position: static;
padding: 0;
margin: 0;
height: 60px;
color: black;
}
.carousel-moreaccessible .carousel-item .carousel-caption p {
padding: 0;
margin: 0;
}
.carousel-moreaccessible .carousel-item .carousel-caption h3 {
font-size: 1.1em;
padding: 0;
margin: 0;
}
.carousel-moreaccessible .carousel-item .carousel-caption a:hover {
background-color: rgb(0 0 0 / 20%);
}
.carousel-moreaccessible .carousel-item .carousel-caption a:focus {
padding: 4px;
border: 2px solid #005a9c;
background-color: transparent;
color: black;
outline: none;
}
- JS
/*
* File: carousel-prev-next.js
*
* Desc: Carousel widget with Previous and Next Buttons that implements ARIA Authoring Practices
*
*/
'use strict';
var CarouselPreviousNext = function (node, options) {
// merge passed options with defaults
options = Object.assign(
{ moreaccessible: false, paused: false, norotate: false },
options || {}
);
// a prefers-reduced-motion user setting must always override autoplay
var hasReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
if (hasReducedMotion.matches) {
options.paused = true;
}
/* DOM properties */
this.domNode = node;
this.carouselItemNodes = node.querySelectorAll('.carousel-item');
this.containerNode = node.querySelector('.carousel-items');
this.liveRegionNode = node.querySelector('.carousel-items');
this.pausePlayButtonNode = null;
this.previousButtonNode = null;
this.nextButtonNode = null;
this.playLabel = 'Start automatic slide show';
this.pauseLabel = 'Stop automatic slide show';
/* State properties */
this.hasUserActivatedPlay = false; // set when the user activates the play/pause button
this.isAutoRotationDisabled = options.norotate; // This property for disabling auto rotation
this.isPlayingEnabled = !options.paused; // This property is also set in updatePlaying method
this.timeInterval = 5000; // length of slide rotation in ms
this.currentIndex = 0; // index of current slide
this.slideTimeout = null; // save reference to setTimeout
// Pause Button
var elem = document.querySelector('.carousel .controls button.rotation');
if (elem) {
this.pausePlayButtonNode = elem;
this.pausePlayButtonNode.addEventListener(
'click',
this.handlePausePlayButtonClick.bind(this)
);
}
// Previous Button
elem = document.querySelector('.carousel .controls button.previous');
if (elem) {
this.previousButtonNode = elem;
this.previousButtonNode.addEventListener(
'click',
this.handlePreviousButtonClick.bind(this)
);
this.previousButtonNode.addEventListener(
'focus',
this.handleFocusIn.bind(this)
);
this.previousButtonNode.addEventListener(
'blur',
this.handleFocusOut.bind(this)
);
}
// Next Button
elem = document.querySelector('.carousel .controls button.next');
if (elem) {
this.nextButtonNode = elem;
this.nextButtonNode.addEventListener(
'click',
this.handleNextButtonClick.bind(this)
);
this.nextButtonNode.addEventListener(
'focus',
this.handleFocusIn.bind(this)
);
this.nextButtonNode.addEventListener(
'blur',
this.handleFocusOut.bind(this)
);
}
// Carousel item events
for (var i = 0; i < this.carouselItemNodes.length; i++) {
var carouselItemNode = this.carouselItemNodes[i];
// support stopping rotation when any element receives focus in the tabpanel
carouselItemNode.addEventListener('focusin', this.handleFocusIn.bind(this));
carouselItemNode.addEventListener(
'focusout',
this.handleFocusOut.bind(this)
);
var imageLinkNode = carouselItemNode.querySelector('.carousel-image a');
if (imageLinkNode) {
imageLinkNode.addEventListener(
'focus',
this.handleImageLinkFocus.bind(this)
);
imageLinkNode.addEventListener(
'blur',
this.handleImageLinkBlur.bind(this)
);
}
}
// Handle hover events
this.domNode.addEventListener('mouseover', this.handleMouseOver.bind(this));
this.domNode.addEventListener('mouseout', this.handleMouseOut.bind(this));
// initialize behavior based on options
this.enableOrDisableAutoRotation(options.norotate);
this.updatePlaying(!options.paused && !options.norotate);
this.setAccessibleStyling(options.moreaccessible);
this.rotateSlides();
};
/* Public function to disable/enable rotation and if false, hide pause/play button*/
CarouselPreviousNext.prototype.enableOrDisableAutoRotation = function (
disable
) {
this.isAutoRotationDisabled = disable;
this.pausePlayButtonNode.hidden = disable;
};
/* Public function to update controls/caption styling */
CarouselPreviousNext.prototype.setAccessibleStyling = function (accessible) {
if (accessible) {
this.domNode.classList.add('carousel-moreaccessible');
} else {
this.domNode.classList.remove('carousel-moreaccessible');
}
};
CarouselPreviousNext.prototype.showCarouselItem = function (index) {
this.currentIndex = index;
for (var i = 0; i < this.carouselItemNodes.length; i++) {
var carouselItemNode = this.carouselItemNodes[i];
if (index === i) {
carouselItemNode.classList.add('active');
} else {
carouselItemNode.classList.remove('active');
}
}
};
CarouselPreviousNext.prototype.previousCarouselItem = function () {
var nextIndex = this.currentIndex - 1;
if (nextIndex < 0) {
nextIndex = this.carouselItemNodes.length - 1;
}
this.showCarouselItem(nextIndex);
};
CarouselPreviousNext.prototype.nextCarouselItem = function () {
var nextIndex = this.currentIndex + 1;
if (nextIndex >= this.carouselItemNodes.length) {
nextIndex = 0;
}
this.showCarouselItem(nextIndex);
};
CarouselPreviousNext.prototype.rotateSlides = function () {
if (!this.isAutoRotationDisabled) {
if (
(!this.hasFocus && !this.hasHover && this.isPlayingEnabled) ||
this.hasUserActivatedPlay
) {
this.nextCarouselItem();
}
}
this.slideTimeout = setTimeout(
this.rotateSlides.bind(this),
this.timeInterval
);
};
CarouselPreviousNext.prototype.updatePlaying = function (play) {
this.isPlayingEnabled = play;
if (play) {
this.pausePlayButtonNode.setAttribute('aria-label', this.pauseLabel);
this.pausePlayButtonNode.classList.remove('play');
this.pausePlayButtonNode.classList.add('pause');
this.liveRegionNode.setAttribute('aria-live', 'off');
} else {
this.pausePlayButtonNode.setAttribute('aria-label', this.playLabel);
this.pausePlayButtonNode.classList.remove('pause');
this.pausePlayButtonNode.classList.add('play');
this.liveRegionNode.setAttribute('aria-live', 'polite');
}
};
/* Event Handlers */
CarouselPreviousNext.prototype.handleImageLinkFocus = function () {
this.liveRegionNode.classList.add('focus');
};
CarouselPreviousNext.prototype.handleImageLinkBlur = function () {
this.liveRegionNode.classList.remove('focus');
};
CarouselPreviousNext.prototype.handleMouseOver = function (event) {
if (!this.pausePlayButtonNode.contains(event.target)) {
this.hasHover = true;
}
};
CarouselPreviousNext.prototype.handleMouseOut = function () {
this.hasHover = false;
};
/* EVENT HANDLERS */
CarouselPreviousNext.prototype.handlePausePlayButtonClick = function () {
this.hasUserActivatedPlay = !this.isPlayingEnabled;
this.updatePlaying(!this.isPlayingEnabled);
};
CarouselPreviousNext.prototype.handlePreviousButtonClick = function () {
this.previousCarouselItem();
};
CarouselPreviousNext.prototype.handleNextButtonClick = function () {
this.nextCarouselItem();
};
/* Event Handlers for carousel items*/
CarouselPreviousNext.prototype.handleFocusIn = function () {
this.liveRegionNode.setAttribute('aria-live', 'polite');
this.hasFocus = true;
};
CarouselPreviousNext.prototype.handleFocusOut = function () {
if (this.isPlayingEnabled) {
this.liveRegionNode.setAttribute('aria-live', 'off');
}
this.hasFocus = false;
};
/* Initialize Carousel and options */
window.addEventListener(
'load',
function () {
var carouselEls = document.querySelectorAll('.carousel');
var carousels = [];
// set example behavior based on
// default setting of the checkboxes and the parameters in the URL
// update checkboxes based on any corresponding URL parameters
var checkboxes = document.querySelectorAll(
'.carousel-options input[type=checkbox]'
);
var urlParams = new URLSearchParams(location.search);
var carouselOptions = {};
// initialize example features based on
// default setting of the checkboxes and the parameters in the URL
// update checkboxes based on any corresponding URL parameters
checkboxes.forEach(function (checkbox) {
var checked = checkbox.checked;
if (urlParams.has(checkbox.value)) {
var urlParam = urlParams.get(checkbox.value);
if (typeof urlParam === 'string') {
checked = urlParam === 'true';
checkbox.checked = checked;
}
}
carouselOptions[checkbox.value] = checkbox.checked;
});
carouselEls.forEach(function (node) {
carousels.push(new CarouselPreviousNext(node, carouselOptions));
});
// add change event to checkboxes
checkboxes.forEach(function (checkbox) {
var updateEvent;
switch (checkbox.value) {
case 'moreaccessible':
updateEvent = 'setAccessibleStyling';
break;
case 'norotate':
updateEvent = 'enableOrDisableAutoRotation';
break;
}
// update the carousel behavior and URL when a checkbox state changes
checkbox.addEventListener('change', function (event) {
urlParams.set(event.target.value, event.target.checked + '');
window.history.replaceState(
null,
'',
window.location.pathname + '?' + urlParams
);
if (updateEvent) {
carousels.forEach(function (carousel) {
carousel[updateEvent](event.target.checked);
});
}
});
});
},
false
);
Here are links to the documentation:
https://www.w3.org/WAI/ARIA/apg/patterns/
https://www.w3.org/WAI/ARIA/apg/practices/
Here is the Mozilla Developer Network documentation:
https://developer.mozilla.org/en-US/docs/Web/API
Build with the Browser Platform. Build With Web Standards. Build accessible experiences. Build with Web Components. Build your way.
Top comments (0)