Carousel (based on a tabpanel)

Carousel (based on a tabpanel)

This carousel pattern is based on an ARIA tab panel pattern. Only one tab panel (carousel item) is visible at a time. There are buttons to go forward, back, or to play/pause the carousel.



Turn on a screen reader to experience this example in action.

HTML Source Code

<section id="myCarousel" class="carousel-tablist" aria-roledescription="carousel" aria-label="Various places">
    <div class="carousel-inner">
      <div class="controls">
        <button class="rotation" type="button">
          <svg width="42" height="34" version="1.1" xmlns="http://www.w3.org/2000/svg" class="svg-play">
            <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>
  
        <div class="tab-wrapper">
          <div role="tablist" aria-label="Slides">
            <button id="carousel-tab-1" type="button" role="tab" aria-label="Slide 1" aria-selected="true" aria-controls="carousel-item-1">
              <svg width="34" height="34" version="1.1" xmlns="http://www.w3.org/2000/svg">
                <circle class="border" cx="16" cy="15" r="10"></circle>
                <circle class="tab-background" cx="16" cy="15" r="8"></circle>
                <circle class="tab" cx="16" cy="15" r="6"></circle>
              </svg>
            </button>
            <button id="carousel-tab-2" type="button" role="tab" tabindex="-1" aria-label="Slide 2" aria-selected="false" aria-controls="carousel-item-2">
              <svg width="34" height="34" version="1.1" xmlns="http://www.w3.org/2000/svg">
                <circle class="border" cx="16" cy="15" r="10"></circle>
                <circle class="tab-background" cx="16" cy="15" r="8"></circle>
                <circle class="tab" cx="16" cy="15" r="6"></circle>
              </svg>
            </button>
            <button id="carousel-tab-3" type="button" role="tab" tabindex="-1" aria-label="Slide 3" aria-selected="false" aria-controls="carousel-item-3">
              <svg width="34" height="34" version="1.1" xmlns="http://www.w3.org/2000/svg">
                <circle class="border" cx="16" cy="15" r="10"></circle>
                <circle class="tab-background" cx="16" cy="15" r="8"></circle>
                <circle class="tab" cx="16" cy="15" r="6"></circle>
              </svg>
            </button>
           
          </div>
        </div>
      </div>
  
      <div id="myCarousel-items" class="carousel-items playing" aria-live="off">
        <div class="carousel-item active" id="carousel-item-1" role="tabpanel" aria-roledescription="slide" aria-label="1 of 6">
          <div class="carousel-image">
            <a href="#" id="carousel-image-1">
              <img src="/assets/js/patterns/images/tempimage01.jpeg" alt="New York City Skyline">
            </a>
          </div>
          <div class="carousel-caption">
            <h2>
              <a href="#"> New York City Skyline </a>
            </h2>
          </div>
          
        </div>
        
  
        <div class="carousel-item" id="carousel-item-2" role="tabpanel" aria-roledescription="slide" aria-label="2 of 6">
          <div class="carousel-image">
            <a href="#" id="carousel-image-2">
              <img src="/assets/js/patterns/images/tempimage02.jpeg" alt="Beach Sunset, California ">
            </a>
          </div>
  
          <div class="carousel-caption">
            <h2>
              <a href="#"> Beach Sunset, California </a>
            </h2>
          </div>
          
        </div>
        
  
        <div class="carousel-item" id="carousel-item-3" role="tabpanel" aria-roledescription="slide" aria-label="3 of 6">
          <div class="carousel-image">
            <a href="#!" id="carousel-image-3">
              <img src="/assets/js/patterns/images/tempimage03.jpeg" alt="Roman statue, Italy ">
            </a>
          </div>
  
          <div class="carousel-caption">
            <h2>
              <a href="#"> Roman statue, Italy </a>
            </h2>
          </div>
          
        </div>
        
  
      </div>
    </div>
  </section>
  <div class="col-sm-1"></div>
<!---
This component has been adapted from an example provided by the W3C, in accordance with the W3C Software and Document License https://www.w3.org/copyright/software-license-2023/
-->
  

JavaScript Source Code

'use strict';

// takes options object: { accessibleCaptions: boolean, autoplay: boolean, playButton: boolean }
// defaults are: { accessibleCaptions: true, autoplay: false, playButton: true }

var CarouselTablist = 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.tablistNode = node.querySelector('[role=tablist]');
  this.containerNode = node.querySelector('.carousel-items');

  this.tabNodes = [];
  this.tabpanelNodes = [];

  this.liveRegionNode = node.querySelector('.carousel-items');
  this.pausePlayButtonNode = document.querySelector(
    '.carousel-tablist .controls button.rotation'
  );

  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

  // initialize tabs
  this.tablistNode.addEventListener('focusin', this.handleTabFocus.bind(this));
  this.tablistNode.addEventListener('focusout', this.handleTabBlur.bind(this));

  var nodes = node.querySelectorAll('[role="tab"]');

  for (var i = 0; i < nodes.length; i++) {
    var n = nodes[i];

    this.tabNodes.push(n);

    n.addEventListener('keydown', this.handleTabKeydown.bind(this));
    n.addEventListener('click', this.handleTabClick.bind(this));

    // initialize tabpanels

    var tabpanelNode = document.getElementById(n.getAttribute('aria-controls'));

    if (tabpanelNode) {
      this.tabpanelNodes.push(tabpanelNode);

      // support stopping rotation when any element receives focus in the tabpanel
      tabpanelNode.addEventListener(
        'focusin',
        this.handleTabpanelFocusIn.bind(this)
      );
      tabpanelNode.addEventListener(
        'focusout',
        this.handleTabpanelFocusOut.bind(this)
      );

      var imageLink = tabpanelNode.querySelector('.carousel-image a');

      if (imageLink) {
        imageLink.addEventListener(
          'focus',
          this.handleImageLinkFocus.bind(this)
        );
        imageLink.addEventListener('blur', this.handleImageLinkBlur.bind(this));
      }
    } else {
      this.tabpanelNodes.push(null);
    }
  }

  // Pause Button
  if (this.pausePlayButtonNode) {
    this.pausePlayButtonNode.addEventListener(
      'click',
      this.handlePausePlayButtonClick.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*/
CarouselTablist.prototype.enableOrDisableAutoRotation = function (disable) {
  this.isAutoRotationDisabled = disable;
  this.pausePlayButtonNode.hidden = disable;
};

/* Public function to update controls/caption styling */
CarouselTablist.prototype.setAccessibleStyling = function (accessible) {
  if (accessible) {
    this.domNode.classList.add('carousel-tablist-moreaccessible');
  } else {
    this.domNode.classList.remove('carousel-tablist-moreaccessible');
  }
};

CarouselTablist.prototype.hideTabpanel = function (index) {
  var tabNode = this.tabNodes[index];
  var panelNode = this.tabpanelNodes[index];

  tabNode.setAttribute('aria-selected', 'false');
  tabNode.setAttribute('tabindex', '-1');

  if (panelNode) {
    panelNode.classList.remove('active');
  }
};

CarouselTablist.prototype.showTabpanel = function (index, moveFocus) {
  var tabNode = this.tabNodes[index];
  var panelNode = this.tabpanelNodes[index];

  tabNode.setAttribute('aria-selected', 'true');
  tabNode.removeAttribute('tabindex');

  if (panelNode) {
    panelNode.classList.add('active');
  }

  if (moveFocus) {
    tabNode.focus();
  }
};

CarouselTablist.prototype.setSelectedTab = function (index, moveFocus) {
  if (index === this.currentIndex) {
    return;
  }
  this.currentIndex = index;

  for (var i = 0; i < this.tabNodes.length; i++) {
    this.hideTabpanel(i);
  }

  this.showTabpanel(index, moveFocus);
};

CarouselTablist.prototype.setSelectedToPreviousTab = function (moveFocus) {
  var nextIndex = this.currentIndex - 1;

  if (nextIndex < 0) {
    nextIndex = this.tabNodes.length - 1;
  }

  this.setSelectedTab(nextIndex, moveFocus);
};

CarouselTablist.prototype.setSelectedToNextTab = function (moveFocus) {
  var nextIndex = this.currentIndex + 1;

  if (nextIndex >= this.tabNodes.length) {
    nextIndex = 0;
  }

  this.setSelectedTab(nextIndex, moveFocus);
};

CarouselTablist.prototype.rotateSlides = function () {
  if (!this.isAutoRotationDisabled) {
    if (
      (!this.hasFocus && !this.hasHover && this.isPlayingEnabled) ||
      this.hasUserActivatedPlay
    ) {
      this.setSelectedToNextTab(false);
    }
  }

  this.slideTimeout = setTimeout(
    this.rotateSlides.bind(this),
    this.timeInterval
  );
};

CarouselTablist.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 */

CarouselTablist.prototype.handleImageLinkFocus = function () {
  this.liveRegionNode.classList.add('focus');
};

CarouselTablist.prototype.handleImageLinkBlur = function () {
  this.liveRegionNode.classList.remove('focus');
};

CarouselTablist.prototype.handleMouseOver = function (event) {
  if (!this.pausePlayButtonNode.contains(event.target)) {
    this.hasHover = true;
  }
};

CarouselTablist.prototype.handleMouseOut = function () {
  this.hasHover = false;
};

/* EVENT HANDLERS */

CarouselTablist.prototype.handlePausePlayButtonClick = function () {
  this.hasUserActivatedPlay = !this.isPlayingEnabled;
  this.updatePlaying(!this.isPlayingEnabled);
};

/* Event Handlers for Tabs*/

CarouselTablist.prototype.handleTabKeydown = function (event) {
  var flag = false;

  switch (event.key) {
    case 'ArrowRight':
      this.setSelectedToNextTab(true);
      flag = true;
      break;

    case 'ArrowLeft':
      this.setSelectedToPreviousTab(true);
      flag = true;
      break;

    case 'Home':
      this.setSelectedTab(0, true);
      flag = true;
      break;

    case 'End':
      this.setSelectedTab(this.tabNodes.length - 1, true);
      flag = true;
      break;

    default:
      break;
  }

  if (flag) {
    event.stopPropagation();
    event.preventDefault();
  }
};

CarouselTablist.prototype.handleTabClick = function (event) {
  var index = this.tabNodes.indexOf(event.currentTarget);
  this.setSelectedTab(index, true);
};

CarouselTablist.prototype.handleTabFocus = function () {
  this.tablistNode.classList.add('focus');
  this.liveRegionNode.setAttribute('aria-live', 'polite');
  this.hasFocus = true;
};

CarouselTablist.prototype.handleTabBlur = function () {
  this.tablistNode.classList.remove('focus');
  if (this.playState) {
    this.liveRegionNode.setAttribute('aria-live', 'off');
  }

  this.hasFocus = false;
};

/* Event Handlers for Tabpanels*/

CarouselTablist.prototype.handleTabpanelFocusIn = function () {
  this.hasFocus = true;
};

CarouselTablist.prototype.handleTabpanelFocusOut = function () {
  this.hasFocus = false;
};

/* Initialize Carousel Tablists and options */

window.addEventListener(
  'load',
  function () {
    var carouselEls = document.querySelectorAll('.carousel-tablist');
    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 CarouselTablist(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
);
    
//This component has been adapted from an example provided by the W3C, in accordance with the W3C Software and Document License https://www.w3.org/copyright/software-license-2023/

CSS Source Code

img.reload {
    padding: 0.25em;
    display: block-inline;
    position: relative;
    top: 6px;
    height: 0.9em;
  }
  
  .carousel-tablist {
    background-color: #eee;
    max-width: 900px;
  }
  
  .carousel-tablist .carousel-inner {
    position: relative;
  }
  
  .carousel-tablist .carousel-items {
    padding: 5px;
  }
  
  .carousel-tablist .carousel-items.focus {
    padding: 2px;
    border: solid 3px #005a9c;
  }
  
  .carousel-tablist .carousel-item {
    display: none;
    max-height: 400px;
    max-width: 900px;
    position: relative;
    overflow: hidden;
    width: 100%;
  }
  
  .carousel-tablist .carousel-item.active {
    display: block;
  }
  
  /* More like bootstrap, less accessible */
  
  .carousel-tablist .carousel-item .carousel-image a img {
    height: 100%;
    width: 100%;
  }
  
  .carousel-tablist .carousel-item .carousel-caption a {
    cursor: pointer;
    text-decoration: underline;
    color: #fff;
    font-weight: 600;
  }
  
  .carousel-tablist .carousel-item .carousel-caption a,
  .carousel-tablist .carousel-item .carousel-caption span.contrast {
    margin: 0;
    padding: 6px;
    display: inline-block;
    background-color: rgb(0 0 0 / 65%);
    border-radius: 5px;
    border: 0 solid transparent;
  }
  
  .carousel-tablist-moreaccessible .carousel-items .carousel-image a {
    display: block;
    margin: 0;
    padding: 5px;
    text-decoration: none;
    border: none;
  }
  
  .carousel-tablist-moreaccessible .carousel-item .carousel-caption a {
    display: inline-block;
    margin: 0;
    padding: 6px;
    color: black;
    background-color: transparent;
    border: none;
    border-radius: 5px;
  }
  
  .carousel-tablist-moreaccessible .carousel-item .carousel-caption span.contrast,
  .carousel-tablist-moreaccessible
    .carousel-item
    .carousel-caption
    span.contrast:hover {
    background-color: transparent;
  }
  
  .carousel-tablist .carousel-item .carousel-caption a:hover,
  .carousel-tablist .carousel-item .carousel-caption span.contrast:hover {
    background-color: rgb(0 0 0 / 100%);
  }
  
  .carousel-tablist .carousel-item .carousel-caption a:focus {
    padding: 4px;
    border: 2px solid #eee;
    background-color: rgb(0 0 0 / 100%);
    outline: none;
    border-width: 2px solid #fff;
    color: #fff;
  }
  
  .carousel-tablist .carousel-item .carousel-caption p {
    font-size: 1em;
    line-height: 1.5;
    margin-bottom: 0;
  }
  
  .carousel-tablist .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 and Tab Controls */
  
  .carousel-tablist .controls {
    box-sizing: border-box;
    position: absolute;
    top: 1em;
    z-index: 10;
    display: flex;
    width: 100%;
    padding: 0.25em 1.25em 0;
  }
  
  .carousel-tablist .tab-wrapper {
    flex: 1 1 auto;
    height: 30px;
    text-align: center;
  }
  
  /* SVG Controls */
  
  .carousel-tablist .rotation {
    flex: 0 0 auto;
    height: 30px;
    padding: 0;
    background-color: transparent;
    border: none;
    outline: none;
    z-index: 10;
  }
  
  .carousel-tablist .svg-play .background {
    stroke: black;
    fill: black;
    stroke-width: 1px;
    opacity: 0.6;
  }
  
  .carousel-tablist .svg-play .border {
    fill: transparent;
    stroke: transparent;
    stroke-width: 2px;
  }
  
  .carousel-tablist .svg-play .pause {
    stroke-width: 4;
    fill: transparent;
    stroke: transparent;
  }
  
  .carousel-tablist .svg-play .play {
    stroke-width: 1;
    fill: transparent;
    stroke: transparent;
  }
  
  .carousel-tablist .pause .svg-play .pause,
  .carousel-tablist .play .svg-play .play {
    fill: white;
    stroke: white;
  }
  
  .carousel-tablist .rotation:focus .svg-play .background,
  .carousel-tablist .rotation:hover .svg-play .background,
  .carousel-tablist .rotation:hover .svg-play .border {
    fill: #005a9c;
    stroke: #005a9c;
    opacity: 1;
  }
  
  .carousel-tablist .rotation:focus .svg-play .border {
    stroke: white;
  }
  
  /* Shared CSS for Tabs */
  
  .carousel-tablist [role="tablist"] {
    box-sizing: border-box;
    border: 0 transparent solid;
    border-radius: 13px;
    display: inline-block;
    padding-top: 2px;
    height: 30px;
    background-color: rgb(0 0 0 / 65%);
  }
  
  .carousel-tablist [role="tablist"].focus {
    border-width: 2px;
    border-color: white;
    padding-top: 0;
    background-color: #005a9c;
  }
  
  .carousel-tablist [role="tab"] {
    position: relative;
    top: -2px;
    padding: 0;
    margin: 0;
    background-color: transparent;
    border: none;
    outline: none;
    width: 34px;
  }
  
  .carousel-tablist [role="tab"] circle.border {
    display: none;
    z-index: 12;
  }
  
  .carousel-tablist [role="tab"] circle.tab {
    z-index: 16;
  }
  
  .carousel-tablist [role="tab"] circle.tab-background {
    stroke: black;
    fill: black;
    stroke-width: 2px;
    opacity: 0.6;
    z-index: 14;
  }
  
  .carousel-tablist [role="tab"] circle.tab,
  .carousel-tablist [role="tab"] circle.border {
    stroke: white;
    fill: transparent;
    stroke-width: 2px;
  }
  
  .carousel-tablist-moreaccessible [role="tab"] circle.tab-background {
    z-index: 16;
  }
  
  .carousel-tablist-moreaccessible [role="tab"] circle.tab {
    z-index: 18;
  }
  
  .carousel-tablist [role="tab"][aria-selected="true"] circle.tab {
    fill: white;
  }
  
  .carousel-tablist-moreaccessible [role="tab"] circle.border {
    z-index: 14;
  }
  
  .carousel-tablist [role="tab"]:focus circle.border {
    display: block;
    fill: #005a9c;
    stroke: #fff;
  }
  
  .carousel-tablist [role="tablist"].focus circle.tab-background {
    stroke: #005a9c;
    fill: #005a9c;
  }
  
  .carousel-tablist [role="tab"]:hover circle.tab-background {
    fill: white;
    stroke: white;
    opacity: 1;
  }
  
  .carousel-tablist [role="tab"]:hover circle.border,
  .carousel-tablist [role="tab"]:hover circle.tab {
    fill: #005a9c;
    stroke: #005a9c;
    opacity: 1;
  }
  
  .carousel-tablist [role="tab"][aria-selected="true"]:hover circle.tab {
    fill: white;
  }
  
  /* More accessible carousel styles, with caption and controls above/below image */
  
  .carousel-tablist-moreaccessible {
    padding: 0;
    margin: 0;
    position: relative;
    border: #eee solid 4px;
    border-radius: 5px;
  }
  
  .carousel-tablist-moreaccessible .carousel-items,
  .carousel-tablist-moreaccessible .carousel-items.focus {
    padding: 0;
    border: none;
  }
  
  .carousel-tablist-moreaccessible .carousel-items.focus .carousel-image a {
    padding: 2px;
    border: 3px solid #005a9c;
  }
  
  /* More accessible caption styling */
  
  .carousel-tablist-moreaccessible .carousel-item {
    padding: 0;
    margin: 0;
    max-height: none;
  }
  
  .carousel-tablist-moreaccessible .carousel-item .carousel-caption {
    position: static;
    padding: 0;
    margin: 0;
    height: 60px;
    color: black;
  }
  
  .carousel-tablist-moreaccessible .carousel-item .carousel-caption p {
    padding: 0;
    margin: 0;
  }
  
  .carousel-tablist-moreaccessible .carousel-item .carousel-caption h3 {
    font-size: 1.1em;
    padding: 0;
    margin: 0;
  }
  
  .carousel-tablist-moreaccessible .carousel-item .carousel-caption a:hover {
    background-color: rgb(0 0 0 / 20%);
  }
  
  .carousel-tablist-moreaccessible .carousel-item .carousel-caption a:focus {
    padding: 4px;
    border: 2px solid #005a9c;
    background-color: transparent;
    color: black;
    outline: none;
  }
  
  /* Shared CSS for Pause and Tab Controls */
  
  .carousel-tablist-moreaccessible .controls {
    position: relative;
    top: 0;
    left: 0;
    padding: 0.25em 0.25em 0;
  }
  
  /* Shared CSS for Tabs */
  
  .carousel-tablist-moreaccessible [role="tab"] {
    z-index: 20;
  }
  
  /*
This component has been adapted from an example provided by the W3C, in accordance with the W3C Software and Document License https://www.w3.org/copyright/software-license-2023/
*/

Copy and Paste Full Page Example