Go to homepage

Projects /

Hero Slider

A full-width, responsive and easy to customize slideshow.

Hero Slider
Check our new component library →

It is a common approach to fill in the intro section of a website with a slideshow: you're trying to show the users as much as you can above the fold, yet you want to deliver this information in an organized and clean way.  Therefore we built for you a ready-to-use JavaScript slider, with some built-in options like video/image backgrounds and different text alignments. In an attempt to increase user engagement, we replaced the "navigation arrows" with buttons. The difference is: buttons have a title, a hint about what kind of content to expect. Arrows just tell users "you can switch slide".


Creating the structure

The HTML is structured in 2 main elements: an unordered list (ul.cd-hero__slider) containing the slides, and a div.cd-hero__nav, containing the slider navigation and the span.cd-hero__marker (used to create the marker for the selected item in the navigation).

<section class="cd-hero js-cd-hero js-cd-autoplay">
   <ul class="cd-hero__slider">
      <li class="cd-hero__slide cd-hero__slide--selected js-cd-slide">
         <div class="cd-hero__content cd-hero__content--full-width">
            <h2><!-- Title here --></h2>
            <p><!-- Content here --></p>
            <a href="#0" class="cd-hero__btn"><!-- Btn text here --></a>
         </div> <!-- .cd-hero__content -->

      <!-- other slides here -->
   </ul> <!-- .cd-hero__slider -->

   <div class="cd-hero__nav js-cd-nav">
         <span class="cd-hero__marker cd-hero__marker--item-1 js-cd-marker"></span>

            <li class="cd-selected"><a href="#0">Intro</a></li>
            <li><a href="#0">Tech 1</a></li>
            <!-- other navigation items here -->
   </div> <!-- .cd-hero__nav -->
</section> <!-- .cd-hero -->

Adding style

The slider structure is pretty straightforward: all the slides are translated to the right, outside the viewport (translateX(100%)); the .cd-hero__slide--selected class is added to the visible slide to move it back into the viewport (translateX(0)), while the .cd-hero__slide--move-left class is used to translate a slide to the left (translateX(-100%)).

To achieve the smooth animation, we used CSS3 Transitions applied to the .cd-hero__slide--selected and the .cd-hero__slide--is-moving elements: when a new slide is selected, the .cd-hero__slide--is-moving class is assigned to the slide moving outside the viewport, while the .cd-hero__slide--selected class is assigned to the selected slide.

.cd-hero__slide {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  transform: translateX(100%);

.cd-hero__slide.cd-hero__slide--selected {
  /* this is the visible slide */
  transform: translateX(0);

.cd-hero__slide.cd-hero__slide--move-left {
  /* slide hidden on the left */
  transform: translateX(-100%);

.cd-hero__slide.cd-hero__slide--selected {
  /* the cd-hero__slide--is-moving class is assigned to the slide which is moving outside the viewport */
  transition: transform 0.5s;

About the single slide animation: on big devices (viewport width more than 768px), we decided to spice up the entrance effect animating the single slide elements (.cd-hero__content--half-width and .cd-hero__content--full-width), changing their opacity and transform properties.

The .cd-hero__slide--from-left and .cd-hero__slide--from-right classes are used to detect if the selected slide is entering the viewport from the left or from the right, in order to trigger a different animation according to the entrance direction. For this effect to properly work, we used a different animation-delay value for each animated element.

For the .cd-hero__content--half-width elements, for example:

@media only screen and (min-width: 768px) {
  .cd-hero__content.cd-hero__content--half-width {
    opacity: 0;
    transform: translateX(40px);

  .cd-hero__slide--move-left .cd-hero__content.cd-hero__content--half-width {
    transform: translateX(-40px);

  .cd-hero__slide--selected .cd-hero__content.cd-hero__content--half-width {
    /* this is the visible slide */
    opacity: 1;
    transform: translateX(0);

  .cd-hero__slide--is-moving .cd-hero__content.cd-hero__content--half-width {
    /* this is the slide moving outside the viewport 
    wait for the end of the transition on the <li> parent before set opacity to 0 and translate to 40px/-40px */
    transition: opacity 0s 0.5s, transform 0s 0.5s;

  .cd-hero__slide--from-left.cd-hero__slide--selected .cd-hero__content.cd-hero__content--half-width:nth-of-type(2),
  .cd-hero__slide--from-right.cd-hero__slide--selected .cd-hero__content.cd-hero__content--half-width:first-of-type {
    /* this is the selected slide - different animation if it's entering from left or right */
    transition: opacity 0.4s 0.2s, transform 0.5s 0.2s;

  .cd-hero__slide--from-left.cd-hero__slide--selected .cd-hero__content.cd-hero__content--half-width:first-of-type,
  .cd-hero__slide--from-right.cd-hero__slide--selected .cd-hero__content.cd-hero__content--half-width:nth-of-type(2) {
    /* this is the selected slide - different animation if it's entering from left or right */
    transition: opacity 0.4s 0.4s, transform 0.5s 0.4s;

Events handling

The video used as background for one of the slides is not inserted directly into the HTML but loaded only if the device width is bigger than 768px; this way the video won't be loaded on mobile devices. The data-video of the selected slide is used to retrieve the video url. You may consider doing the same for the <img> elements inside the .cd-hero__content--img (which is hidden on mobile devices).

Besides, we used JavaScript to implement the slideshow functionality: when user clicks one of the list items of the .cd-hero__nav tab, we detect the position of the selected item and update the slider and the span.cd-hero__marker position accordingly.

To do that, we created a HeroSlider object and used the init function to attach event handlers to the proper elements:

function HeroSlider( element ) {
  this.element = element;
  this.navigation = this.element.getElementsByClassName("js-cd-nav")[0];
  this.navigationItems = this.navigation.getElementsByTagName('li');
  this.marker = this.navigation.getElementsByClassName("js-cd-marker")[0];
  this.slides = this.element.getElementsByClassName("js-cd-slide");
  this.slidesNumber = this.slides.length;
  // ...

HeroSlider.prototype.init = function() {
  // ...

  //listen for the click event on the slider navigation
  this.navigation.addEventListener('click', function(event){
    if( event.target.tagName.toLowerCase() == 'div' )
    var selectedSlide = event.target;
    if( hasClass(event.target.parentElement, 'cd-selected') )
    self.oldSlideIndex = self.newSlideIndex;
    self.newSlideIndex = Array.prototype.indexOf.call(self.navigationItems, event.target.parentElement);


var heroSliders = document.getElementsByClassName("js-cd-hero");
if( heroSliders.length > 0 ) {
  for( var i = 0; i < heroSliders.length; i++) {
      new HeroSlider(heroSliders[i]);

Note: if you want to animate the slider automatically, add the .js-cd-autoplay class to the .cd-hero element.

Project duplicated

Project created

Globals imported

There was an error while trying to export your project. Please try again or contact us.