Shepherd JS Example: Guided tours to onboard your users fast!

This article is going to cover the usage of Shepherd JS. You will look at a detailed Shepherd JS example. I have tried to cover many of the situations that might pop up as you are trying to build a guided tour for your users.

A Working Example Of User On-boarding With Shepherd.JS

To have a clear idea of what you will get out of this guide, I have made a working demo (Does not work well on mobile. If on a mobile device, see video below). Most of the code snippets from this guide come from that working example.

To have something that looks very much like a real SAAS application I use the CoreUI, Free Bootstrap Admin Template as the place where I implemented ShepherdJS.

Below is a video I made to cover all the different situations covered in the working demo..

Step By Step Guide To Using The Library

Step 1: Installation (Via CDN)

The official docs provide a few different ways to add ShepardJS to your project. But the easiest is the CDN.

All you have to do is plop in the CSS and JS links into your header and footer section as needed.

Below are the CDN links as they stand today for your easy copy pasting..

<!-- The Stylesheet -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/shepherd.js@8.3.1/dist/css/shepherd.css"/>


<!-- The JS Script Tag -->
<script src="https://cdn.jsdelivr.net/npm/shepherd.js@8.3.1/dist/js/shepherd.min.js"></script>

Step 2: Create A Tour

The first thing you do is create a new variable to hold your guided tour. (In the next section, you will add “steps” to your tour)

To create your tour you can do something like this..

const tour = new Shepherd.Tour({
    // Tour options
    useModalOverlay: true,
    // Options you want to set for all steps
    defaultStepOptions: {
        exitOnEsc: true
    }
});

As you can see, it is very simple to create a tour. All you need to do is pass in a JSON object that contains all the options you are going for.

All Possible Tour Options

These options allow you to control the behavior of the “guided tour” as a whole (simplified form the official docs for reference) …

Option Description
classPrefix Prefix added to the class names of the ShepardJS created elements. This to helps with CSS element selectors for custom styling.
confirmCancel If true, will trigger a window.confirm before cancelling. Just so that the user does not accidently exit out of the tour.
confirmCancelMessage The message to display in the confirm dialog created with the above option.
defaultStepOptions Default options for Steps created through addStep. Saves you from repeating step related options each time.
exitOnEsc Exiting the tour with the escape key will be enabled unless this is explicitly set to false.
keyboardNavigation Navigating the tour via left and right arrow keys will be enabled unless this is explicitly set to false.
stepsContainer An optional container element for the steps. If not set, the steps will be appended to document.body.
modalContainer An optional container element for the modal. If not set, the modal will be appended to document.body.
steps An array of step options objects or Step instances to initialize the tour with. (We will be adding the steps later and not using this.)
tourName An optional “name” for the tour. This will be appended to the the tour’s dynamically generated id property — which is also set on the body element as the data-shepherd-active-tour attribute whenever the tour becomes active. Also can be used for theming.
useModalOverlay Whether or not steps should be placed above a darkened modal overlay. If true, the overlay will create an opening around the target element so that it can remain bright.

The Options We Have Used

const tour = new Shepherd.Tour({
    // Tour options
    useModalOverlay: true,
    // Options you want to set for all steps
    defaultStepOptions: {
        exitOnEsc: true
    }
});

Above we have used the useModalOverlay to create a dark overly over the UI and only highlight the element that needs to be focused. This is much more clear from the UX perspective. So, I consider it a pretty essential option.

We have also used defaultStepOptions just to show that you can set up some options for all steps at once when setting up the tour. We have turned on  exitOnEsc for all steps. Just so that the user can hit ESC at any time and exit the tour.

Step 3: Set Up The Steps

Next, after the tour is setup, you can create all the individual steps.

The code for setting up a single set is as follows (heavily commented so that you know what is going on):

// call "addStep" to add a 
// step to the tour we just created
tour.addStep({
    id: 'step1',

    // The text to show
    text: 'Users seem to be down by 12.4%. Is that bad?',
    
    // Do we need to 
    // scroll if the element is off page?
    scrollTo: {
        behavior: 'smooth',
        block: 'center'
    },

    // What is the element to highlight
    attachTo: {
        element: 'body > div.wrapper.d-flex.flex-column.min-vh-100.bg-light > div > div > div:nth-child(1) > div:nth-child(1) > div',
        on: 'bottom'
    },

    // An array of buttons under the message
    // so that the user goes next or maybe 
    // exits the tour.
    buttons: [
      {
        text: 'Next',
        action: tour.next
      },
      {
        text: 'Exit Tour',
        action: tour.cancel
      },
    ]

});

As you can see, you just call “addStep” on the tour variable you created in the last section and then pass in a JSON object with all the options you want. Its the same pattern as above.

All the options you can use when you create the steps can be seen in the office docs here. But, above you can see most of the important ones. The example above should help you set things up in 90% of cases. (I will cover some interesting ways to set up steps, and use cases below.)

So, the idea is that you keep adding all the steps till you are done. Here is my code from the working example..

tour.addStep({
    id: 'step1',
    text: 'Users seem to be down by 12.4%. Is that bad?',
    scrollTo: {
        behavior: 'smooth',
        block: 'center'
    },
    attachTo: {
        element: 'body > div.wrapper.d-flex.flex-column.min-vh-100.bg-light > div > div > div:nth-child(1) > div:nth-child(1) > div',
        on: 'bottom'
    },
    buttons: [{
        text: 'Next',
        action: tour.next
    }]
});

tour.addStep({
    id: 'step2',
    text: 'Income is up by 40%!! I guess the users who left were dead weight.',
    attachTo: {
        element: 'body > div.wrapper.d-flex.flex-column.min-vh-100.bg-light > div > div > div:nth-child(1) > div:nth-child(2) > div',
        on: 'bottom'
    },
    buttons: [{
        text: 'Next, We Scroll!',
        action: tour.next
    }]
});

tour.addStep({
    id: 'step3',
    text: 'Hey! We look at this guys smiling face. Also, notice we scrolled down to come here!',
    scrollTo: {
        behavior: 'smooth',
        block: 'center'
    },
    attachTo: {
        element: 'body > div.wrapper.d-flex.flex-column.min-vh-100.bg-light > div > div > div:nth-child(4) > div > div > div.card-body > div.table-responsive > table > tbody > tr:nth-child(6) > td:nth-child(1) > div > img',
        on: 'bottom'
    },
    buttons: [{
        text: 'Next',
        action: tour.next
    }]
});

tour.addStep({
    id: 'step4',
    text: 'Now we are going to simulate a user click on this, open up the menu below & then continue.',
    scrollTo: {
        behavior: 'smooth',
        block: 'center'
    },
    attachTo: {
        element: "#sidebar > ul > div.simplebar-wrapper > div.simplebar-mask > div > div > div > li:nth-child(6) > a",
        on: 'bottom'
    },
    buttons: [{
        text: 'Next',
        action: tour.next
    }]
});


tour.addStep({
    id: 'step5',
    text: 'Here we made this element visible & then showed the step. Cool right?',
    scrollTo: {
        behavior: 'smooth',
        block: 'center'
    },
    attachTo: {
        element: '#sidebar > ul > div.simplebar-wrapper > div.simplebar-mask > div > div > div > li.nav-group.show > ul > li:nth-child(5) > a',
        on: 'bottom'
    },
    beforeShowPromise: function() {
        return new Promise(function(resolve) {
            document.querySelector("#sidebar > ul > div.simplebar-wrapper > div.simplebar-mask > div > div > div > li:nth-child(6) > a").click();
            resolve();
        });
    },
    buttons: [{
        text: 'Next, We  Go To A New Page!',
        action: function() {
            window.location.href = "/base/carousel.html?shtc=t";
        }
    }]
});

A Note About Using The attachTo Option To Attach A Step To An Element On The Page

In the above code, you will see the “attachTo” option the in step looking something like:

attachTo: {
    element: 'body > div.wrapper.d-flex.flex-column.min-vh-100.bg-light > div > div > div:nth-child(4) > div > div > div.card-body > div.table-responsive > table > tbody > tr:nth-child(6) > td:nth-child(1) > div > img',
    on: 'bottom'
}

You can choose to give your elements some nice names like: “#stats_widget” and then the selector for the “element” option would be really simple like: “#stats_widget”.

But, as you can see above, I have just copied the selector that I got directly from the browser developer tools. This was quick an easy and worked out fine. I did not need to add any HTML.

Below is screenshot of what I mean..

copy a css selector directly from the browser for attaching in the shepardjs example

Step 4: Running The Tour

Finally, when all your steps are ready, you can start the tour with a single line like so..

// Just call start on the tour
// variable created in step 1
tour.start();

Notice that this means that you can start the tour when a user clicks on a button.

Or you can set up multiple tours (see last part of the article) using different tour variables.  Then you can start the one you need depending on the situation.

Step 3: Styling & Theming

The HTML that the ShepherdJS library injects into the page looks something like this:

<div aria-describedby="step1-description" role="dialog" tabindex="0" class="shepherd-element shepherd-enabled" data-shepherd-step-id="step1" style="position: absolute; inset: 0px auto auto 0px; margin: 0px; transform: translate(393px, 302px);" data-popper-placement="bottom">
   <div class="shepherd-arrow" data-popper-arrow="" style="position: absolute; left: 0px; transform: translate(192px);"></div>
   <div class="shepherd-content">
      <div class="shepherd-text" id="step1-description">Users seem to be down by 12.4%. Is that bad?</div>
      <footer class="shepherd-footer"><button class=" shepherd-button " tabindex="0">Next</button></footer>
   </div>
</div>

So, it should be fairly easy to target elements (for example the button) with something like..

.shepherd-element footer button {
  /* Some styles here... */
}

Besides this, also notice that in the HTML, there in an ID like: “#step1-description”. This is coming from the “id” option we gave to the step. So, that can also be used to target the description of a particular step.

How To: Make An Element Visible & Then Continue The Guide : Shepherd JS beforeShowPromise Example

This is part of the demo. The situation here is that there is an element. But at first glance it is hidden. For example, its in a sub-menu that shows up only when the menu is opened etc.

So, in this case, when creating a step, you can use the: beforeShowPromise option. Below is some sample code..

tour.addStep({
    id: 'step5',
    text: 'Here we made this element visible & then showed the step. Cool right?',
    scrollTo: {
        behavior: 'smooth',
        block: 'center'
    },
    attachTo: {
        element: '#sidebar > ul > div.simplebar-wrapper > div.simplebar-mask > div > div > div > li.nav-group.show > ul > li:nth-child(5) > a',
        on: 'bottom'
    },

    // Here we run a function.
    // In the function we click on an element
    // and then we call resolve.
    // Once thats done, the displaying to the step
    // continues as normal.
    beforeShowPromise: function() {
        return new Promise(function(resolve) {
            // Using JS To click on an element
            document.querySelector("#sidebar > ul > div.simplebar-wrapper > div.simplebar-mask > div > div > div > li:nth-child(6) > a").click();
            resolve();
        });
    },

    buttons: [{
        text: 'Next, We  Go To A New Page!',
        action: function() {
            window.location.href = "/base/carousel.html?shtc=t";
        }
    }]
});

The description from the official docs is as follows:

A function that returns a promise. When the promise resolves, the rest of the show code for the step will execute.

In case you have not seen this in action, do check out the live demo (does not work well on mobile).

How To: Scroll Down To Element & Then Continue The Guide

To achieve this, you just need to use a step option. Have already shown it above, but created this section just to highlight this point.

Below is a example of a Shepherd JS step that makes the element scroll into view, before continuing.

tour.addStep({
    id: 'step4',
    text: 'Now we are going to simulate a user click on this, open up the menu below & then continue.',

    // This is the important option
    // to make the element scroll into view
    // Not much information is given about 
    // the sub options.
    scrollTo: {
        behavior: 'smooth',
        block: 'center'
    },
    
    
    attachTo: {
        element: "#sidebar > ul > div.simplebar-wrapper > div.simplebar-mask > div > div > div > li:nth-child(6) > a",
        on: 'bottom'
    },
    buttons: [{
        text: 'Next',
        action: tour.next
    }]
});

How To: Continue The Tour After Page Navigation

You have to achieve this in 2 parts. The first part is to set up the “action” on the step button to go to the next page. Below is the code snippet. I am sending a GET param in the URL that says that the tour should continue: shtc=t (Should tour continue = shtc)

Lets looks at the code for the first part..

tour.addStep({
    id: 'step5',
    text: 'Here we made this element visible & then showed the step. Cool right?',
    scrollTo: {
        behavior: 'smooth',
        block: 'center'
    },
    attachTo: {
        element: '#sidebar > ul > div.simplebar-wrapper > div.simplebar-mask > div > div > div > li.nav-group.show > ul > li:nth-child(5) > a',
        on: 'bottom'
    },
    beforeShowPromise: function() {
        return new Promise(function(resolve) {
            document.querySelector("#sidebar > ul > div.simplebar-wrapper > div.simplebar-mask > div > div > div > li:nth-child(6) > a").click();
            resolve();
        });
    },
    buttons: [{
        text: 'Next, We  Go To A New Page!',

        // Here is the imp part
        // we are using JS to send the user
        // to a new page with the GET param: shtc=t
        // We will catch this on the other side.
        action: function() {
            window.location.href = "/base/carousel.html?shtc=t";
        }
    }]
});

Now, on the other page, we will check to see if we got this, param. If we did, we will continue the tour there. Below is the code for the same..

// Set up a tour here

// Some JS code to check the
// the value of the 'shtc' param
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const shouldTourContinue = urlParams.get('shtc');

// Start the tour if we get the correct
// GET param value
if (shouldTourContinue == 't') {
    tour.start();
}

So, if the user just navigates to the second page (not as part of the tour), the tour will not continue since there will be no special GET parameter.

How To: Not Show The Tour To A User Who Has Seen It

This can be done easily using localStorage. All you need to do is: If a value is NOT stored in localStorage, start the tour. Else, do not start it.

Here is a code sample:

if ( localStorage.getItem("seen_tour") == null ) {
    tour.start();
    localStorage.setItem("seen_tour", "true");
}

NOTE: The above code will make it such that the tour runs only once. If you need it to run more than once, then maybe you will need to create an explicit button somewhere that starts the tour.

How To: Have Multiple Tours On A Single Page

Well, all you need to do is create multiple tour objects & associate the steps to each of them. Then, based on user action, start the correct tour. Some sample code…

// Set up the first tour

const tour1 = new Shepherd.Tour({
    useModalOverlay: true,
    classPrefix: 't1_theme',
    defaultStepOptions: {
        exitOnEsc: true
    }
});


tour1.addStep({
    id: 'step1',
    text: 'Users seem to be down by 12.4%. Is that bad?',
    // Other options
});

/// Add more steps to Tour 1




// Set up the second tour tour

const tour2 = new Shepherd.Tour({
  useModalOverlay: true,
  classPrefix: 't1_theme',
  defaultStepOptions: {
      exitOnEsc: true
  }
});


tour2.addStep({
  id: 'step1',
  text: 'Users seem to be down by 12.4%. Is that bad?',
  // Other options
});

/// Add more steps to Tour 2




// Connect tours to on click
// listeners

document.querySelector("#tour_1_btn").addEventListener("click", () => {
  tour1.start();
});


document.querySelector("#tour_2_btn").addEventListener("click", () => {
  tour2.start();
});

Conclusion

The above is a detailed Shepherd.JS example that tries to cover all sort of different use cases and situations. Hope this helps you achieve your goals.