<div class="slider slider-2 position-relative">
    <div class="slider-port" role="region" aria-label="slider" tabindex="0">
        <ul class="slides sibling-margins-skip">
            <li class="slide">
                <div class="card">
                    <div class="card-img-scalable">
                        <img data-lazyimage="../../img/unsplash/alex-knight-4NJoYCuWDok-unsplash.jpg" alt="alt tag for the image" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' /%3E"> </div>
                    <div class="card-body">
                        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
                        <p><a href="#">Learn More</a></p>
                    </div>
                </div>
            </li>
            <li class="slide">
                <div class="card">
                    <div class="card-img-scalable">
                        <img data-lazyimage="../../img/unsplash/beasty--HxIhfS_dUk-unsplash.jpg" alt="alt tag for the image" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' /%3E"> </div>
                    <div class="card-body">
                        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
                        <p><a href="#">Learn More</a></p>
                    </div>
                </div>
            </li>
            <li class="slide">
                <div class="card">
                    <div class="card-img-scalable">
                        <img data-lazyimage="../../img/unsplash/danist-LZ7-lLCRS3s-unsplash.jpg" alt="alt tag for the image" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' /%3E"> </div>
                    <div class="card-body">
                        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
                        <p><a href="#">Learn More</a></p>
                    </div>
                </div>
            </li>
            <li class="slide">
                <div class="card">
                    <div class="card-img-scalable">
                        <img data-lazyimage="../../img/unsplash/nori-webb-fORnjg5ahmM-unsplash.jpg" alt="alt tag for the image" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' /%3E"> </div>
                    <div class="card-body">
                        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
                        <p><a href="#">Learn More</a></p>
                    </div>
                </div>
            </li>
            <li class="slide">
                <div class="card">
                    <div class="card-img-scalable">
                        <img data-lazyimage="../../img/unsplash/oleksii-drozdov-aFHxJx-aKMk-unsplash.jpg" alt="alt tag for the image" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' /%3E"> </div>
                    <div class="card-body">
                        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
                        <p><a href="#">Learn More</a></p>
                    </div>
                </div>
            </li>
            <li class="slide">
                <div class="card">
                    <div class="card-img-scalable">
                        <img data-lazyimage="../../img/unsplash/thomas-drouault-Y1UtWeiRmhE-unsplash.jpg" alt="alt tag for the image" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' /%3E"> </div>
                    <div class="card-body">
                        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
                        <p><a href="#">Learn More</a></p>
                    </div>
                </div>
            </li>
        </ul>
    </div>
</div>
<div class="slider{{slider_type}} position-relative">
  <div class="slider-port" role="region" aria-label="slider" tabindex="0">
    <ul class="slides sibling-margins-skip">
      {{#each slides}}
      <li class="slide">
        {{> @image-card }}
      </li>
      {{/each}}
    </ul>
  </div>
</div>
{
  "slider_type": " slider-2",
  "slides": [
    {
      "lazyimage_src": "../../img/unsplash/alex-knight-4NJoYCuWDok-unsplash.jpg",
      "lazyimage_alt": "alt tag for the image",
      "image-card_body": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p><p><a href=\"#\">Learn More</a></p>"
    },
    {
      "lazyimage_src": "../../img/unsplash/beasty--HxIhfS_dUk-unsplash.jpg",
      "lazyimage_alt": "alt tag for the image",
      "image-card_body": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p><p><a href=\"#\">Learn More</a></p>"
    },
    {
      "lazyimage_src": "../../img/unsplash/danist-LZ7-lLCRS3s-unsplash.jpg",
      "lazyimage_alt": "alt tag for the image",
      "image-card_body": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p><p><a href=\"#\">Learn More</a></p>"
    },
    {
      "lazyimage_src": "../../img/unsplash/nori-webb-fORnjg5ahmM-unsplash.jpg",
      "lazyimage_alt": "alt tag for the image",
      "image-card_body": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p><p><a href=\"#\">Learn More</a></p>"
    },
    {
      "lazyimage_src": "../../img/unsplash/oleksii-drozdov-aFHxJx-aKMk-unsplash.jpg",
      "lazyimage_alt": "alt tag for the image",
      "image-card_body": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p><p><a href=\"#\">Learn More</a></p>"
    },
    {
      "lazyimage_src": "../../img/unsplash/thomas-drouault-Y1UtWeiRmhE-unsplash.jpg",
      "lazyimage_alt": "alt tag for the image",
      "image-card_body": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p><p><a href=\"#\">Learn More</a></p>"
    }
  ]
}
  • Content:
    document.addEventListener("DOMContentLoaded", function() {
      if ("IntersectionObserver" in window) {
        // find all the sliders
        var sliders = [].slice.call(document.querySelectorAll(".slider"));
    
        // function to scroll to a certain slide
        var scrollIt = function scrollIt(slider,slideToShow) {
          var slides = slider.querySelectorAll('.slide');
          var sliderPort = slider.querySelector('.slider-port');
          var scrollPos = slideToShow.offsetLeft;
          sliderPort.scrollLeft = scrollPos;
          setTimeout(function(){ sliderPort.scrollLeft = scrollPos; },75); // i am doing this because there is something strage about setting scroll left position
        };
    
        // function to show a slide
        var showSlide = function showSlide(slider,direction) {
          var slides = slider.querySelectorAll('.slide');
          var sliderPort = slider.querySelector('.slider-port');
          var visible = slider.querySelectorAll('.visible');
          var max_visible = Math.round(sliderPort.offsetWidth / slides[0].offsetWidth);
          var i = direction === 'previous' ? 0 : 1;
    
          // if more than one is visible, scroll to the last visible
          if (visible.length > max_visible) {
            // if next
            if (direction) {
              scrollIt(slider,visible[1]);
            }
            // if previous
            else {
              scrollIt(slider,visible[0])
            }
          }
          else {
            var newSlide = i === 0 ? visible[0].previousElementSibling : visible[0].nextElementSibling;
            if (newSlide) {
              scrollIt(slider,newSlide);
            }
          }
        };
    
        // do for each slider
        sliders.forEach(function(slider) {
          // find all the slides
          var slides = slider.querySelectorAll('.slide');
    
          // the slider port is the container for the slides
          var sliderPort = slider.querySelector('.slider-port');
    
          // set the default tab index such that the slide links cannot be focused
          Array.prototype.forEach.call(slides, function (slide) {
            if (slide.querySelector('a')) {
              slide.querySelector('a').setAttribute('tabindex', '-1');
            }
          });
    
          // settings for intersection observer
          var observerSettings = {
            root: sliderPort
          };
    
          // observe for when slide is visible
          var slide_observer = new IntersectionObserver(function callback(slides, observer) {
            Array.prototype.forEach.call(slides, function (entry) {
              var link = entry.target.querySelector('a');
              if (entry.isIntersecting) {
                // if the slide is in the port, make it visible and focusable
                entry.target.classList.add('visible');
                if (link) { link.removeAttribute('tabindex', '-1'); }
              }
              else {
                // otherwise, make it not visible and not focusable
                entry.target.classList.remove('visible');
                if (link) { link.setAttribute('tabindex', '-1'); }
              }
            });
          }, observerSettings);
    
          Array.prototype.forEach.call(slides, function(slide) {
            // observe all the slides
            return slide_observer.observe(slide);
          });
    
          // observe for the start of the slides
          var start_observer = new IntersectionObserver(function callback(slides, observer) {
            Array.prototype.forEach.call(slides, function (entry) {
              if (entry.isIntersecting) {
                entry.target.parentNode.parentNode.parentNode.querySelector('.slider-control-prev').setAttribute('disabled','disabled');
              }
              else {
                entry.target.parentNode.parentNode.parentNode.querySelector('.slider-control-prev').removeAttribute('disabled');
              }
            });
          }, observerSettings);
    
          start_observer.observe(slides[0]);
    
          // observe for the end of the slides
          var end_observer = new IntersectionObserver(function callback(slides, observer) {
            Array.prototype.forEach.call(slides, function (entry) {
              if (entry.isIntersecting) {
                entry.target.parentNode.parentNode.parentNode.querySelector('.slider-control-next').setAttribute('disabled','disabled');
              }
              else {
                entry.target.parentNode.parentNode.parentNode.querySelector('.slider-control-next').removeAttribute('disabled');
              }
            });
          }, observerSettings);
    
          end_observer.observe(slides[slides.length - 1]);
    
          // add controls
          var controls = document.createElement('ul');
          controls.setAttribute('aria-label', 'slider controls');
          controls.className = "slider-controls list-unstyled p-0 m-0";
          controls.innerHTML = '\n  <li class="d-block p-0 m-0"><button class="slider-control slider-control-prev arrow prev" aria-label="previous">Previous</button></li>\n  <li class="d-block p-0 m-0"><button class="slider-control slider-control-next arrow next" aria-label="next">Next</button></li>';
          slider.append(controls);
    
          // add event handlers to controls
          var prev = controls.querySelector('.slider-control-prev');
          var next = controls.querySelector('.slider-control-next');
          prev.addEventListener('click', function(e) {
            e.preventDefault();
            showSlide(slider,'previous');
          });
          next.addEventListener('click', function(e) {
            e.preventDefault();
            showSlide(slider,'next');
          });
        });
      }
    });
  • URL: /components/raw/slider/slider.js
  • Filesystem Path: source/patterns/organisms/slider/slider.js
  • Size: 5.3 KB
  • Content:
    $break-slider-control:640px;
    $break-slider-2:600px;
    $break-slider-3:900px;
    .slider {
      .slider-port {
        margin-left:auto;
        margin-right:auto;
        overflow-x:scroll;
        position:relative;
        scroll-snap-type:x mandatory;
        scroll-behavior:smooth;
        -webkit-overflow-scrolling:touch;
        width:100%;
        @media (min-width:$break-slider-control) {
          width:calc(100% - 5rem);
        }
      }
      .slides {
        display:flex;
        padding:0;
        padding-bottom:1rem;
        .slide {
          display:block;
          flex-shrink:0;
          margin:0;
          padding:0;
          scroll-snap-align:start;
          width:100%;
        }
        .slide + .slide {
          margin-left:16px;
        }
      }
      .slider-controls {
        @media (max-width:$break-slider-control - 1px) {
          display:flex;
          justify-content:space-between;
        }
      }
      .slider-control {
        background:transparent;
        border:0;
        padding:0;
        @media (min-width:$break-slider-control) {
          position:absolute;
          top:50%;
          &.slider-control-prev {
            left:0;
          }
          &.slider-control-next {
            right:0;
          }
        }
      }
      &.slider-2 {
        .slides {
          .slide {
            @media (min-width:$break-slider-2) {
              width:calc(50% - (16px / 2));
            }
          }
        }
      }
      &.slider-3 {
        .slides {
          .slide {
            @media (min-width:$break-slider-2) {
              width:calc(50% - (16px / 2));
            }
            @media (min-width:$break-slider-3) {
              width:calc(33.333% - (32px / 3));
            }
          }
        }
      }
    }
    
  • URL: /components/raw/slider/slider.scss
  • Filesystem Path: source/patterns/organisms/slider/slider.scss
  • Size: 1.5 KB

Slider

This slider pattern is based on work done by Heydon Pickering for his Inclusive Components project. The goal is to create a version of the slider pattern (sometimes called a carousel) that is efficient (in terms of code) and accessible.

Pickering’s example is 3 years old at the time of this writing and some of the code no longer works. It was necessary to modify or rebuild some of the code to work with the current web standards. I also added some functions and removed others.

Features

  • Accessible
  • Responsive
  • One, two, or three item configurations
  • Start/end detection
  • Button, scroll, and keyboard operation

Notes

This pattern uses Bootstrap cards as the example carousel content. However, it should work for all kinds of elements.

Alternatives

Slick

Slick is a feature-packed carousel option that unfortunately has some accessibility issues. This slider may have fewer options than Slick, but it also requires less code and is more accessible.

Bootstrap

Bootstrap has a carousel component, but it is intended for only one item at a time. The slider presented here is more flexible.