1. Code
  2. JavaScript

How to Build a User Tour With Shepherd in JavaScript

Scroll to top
This post is part of a series called Building Your Startup With PHP.
Using Faker to Generate Filler Data for Automated Testing
Programming With Yii: Generating Documentation
What You'll Be Creating
shepherdshepherdshepherd

Regardless of how simple we try to make our web applications, it's often helpful to guide new users through their first experience. Visual tours are likely the easiest way.

If you've followed my Envato Tuts+ Building Your Startup With PHP series, you're familiar with Meeting Planner. After watching users scheduling their first meeting, I decided it would be best to build some kind of guide.

At first, I considered building it myself, but then I found an open-source option, Shepherd.

In today's tutorial, I'll introduce you to building a visual user tour with Shepherd. Using Shepherd is relatively straightforward, and I'll review some of my own code that I used to streamline the authoring process.

Shepherd is an open-source offering from HubSpot, an inbound marketing service. Kudos to them for offering a robust library with good documentation.

How Shepherd Works

Let's walk through a simple scenario for Shepherd.

Integrating a basic tour to your application with Shepherd is straightforward. First, you choose a theme file and integrate their JavaScript like this:

1
<link rel="stylesheet" href="shepherd-theme-arrows.css" />
2
<script src="tether.min.js"></script>
3
<script src="shepherd.min.js"></script>

You can download the files from the Shepherd GitHub page. I'm using the shepherd-theme-arrows.css above, but you can choose from any of the defaults below and customize them:

Next, you create a tour object:

1
const tour = new Shepherd.Tour({
2
  defaultStepOptions: {
3
    classes: 'shepherd-theme-arrows',
4
    scrollTo: true
5
  }
6
});

The defaults can be defined for all steps when you create the tour using the defaultStepOptions key. The classes refer to the theme definitions you used, e.g. shepherd-theme-arrows, and scrollTo helpfully ensures all steps appear in the visible viewport with the help of the scrollIntoView() method.

Then, you add individual steps to the tour:

1
tour.addStep('example-step', {
2
  text: 'This step is attached to the bottom of the <code>. 

3
example-css-selector</code> element.',
4
  attachTo: { element: '.example-css-selector', on: 'bottom'},
5
  classes: 'example-step-extra-class',
6
  buttons: [
7
    {
8
      text: 'Next',
9
      action: tour.next
10
    }
11
  ]
12
});

The text is what appears in the body of the visual tour. The text can either be a regular HTML string or an HTMLElement object. You can also provide a callback function here that will be executed when the step is built. However, it must return either an HTML string or an HTMLElement object.

The attachTo key points to a CSS selector for the item to which you want to attach this step. It expects an object as its value.

The buttons key allows you to define one or more buttons and their actions, eg. Next. This key accepts an array of button objects as its value. The button objects will have key-value pairs that control the buttons' behavior and appearance.

And finally, you start the tour:

1
tour.start();

Shepherd builds on Tether, another HubSpot open-source offering, which helps position elements to other elements on a page. Tether makes sure your steps never overflow the screen or get cropped.

Integrating Tether Into Your Own Application

As I began experimenting with Shepherd, I discovered quickly that authoring a guide with many steps could get fairly lengthy. This is something I addressed in my own implementation.

I didn't want to author the tour with a lengthy bundle of JavaScript code which would need to be maintained over time. Instead, I chose to create an array and programmatically customize the buttons based on whether users were at the beginning or the end of the tour.

For example, I create a steps[] array and defined the tour by populating the array:

1
const tour = new Shepherd.Tour({
2
  defaultStepOptions: {
3
    classes: 'shepherd-theme-arrows',
4
    scrollTo: true
5
  }
6
});
7
8
const steps = [];
9
10
steps.push({
11
  attachTo: {
12
    element: '.nav-tabs',
13
    on: 'top'
14
  },
15
  title: 'Welcome',
16
  text: `Allow me to show you how to plan a ${title}. <p>If you prefer, you can <a href="javascript::return false;" onclick="turnOffGuide();">turn off this guide</a>.<br /><br />`
17
});
18
steps.push({
19
  attachTo: {
20
    element: '#headingWho',
21
    on: 'top'
22
  },
23
  title: 'Who would you like to invite?',
24
  text: `You can add one person or a group of people to your ${title}. <p>Click the person button to add participants.</p>`
25
});
26
steps.push({
27
  attachTo: {
28
    element: '#invitation-url',
29
    on: 'bottom'
30
  },
31
  title: 'Inviting by email',
32
  text: 'Alternately, you can email the meeting link to your participant(s)'
33
});
34
steps.push({
35
  attachTo: {
36
    element: '#headingWhat',
37
    on: 'bottom'
38
  },
39
  title: 'What is your meeting about?',
40
  text: `You can customize the subject of your ${title}. We'll use it for the invitation and reminder emails.<p>Click the pencil button to edit the subject.</p>`
41
});
42
if ($('#headingActivity').length > 0) {
43
  steps.push({
44
    attachTo: {
45
      element: '#headingActivity',
46
      on: 'top'
47
    },
48
    title: 'What do you want to do?',
49
    text: 'You can suggest one or more activity ideas. With multiple ideas, your participants can help you select their favorite. <p>Click the plus button to suggest activities.</p>'
50
  });
51
}
52
steps.push({
53
  attachTo: {
54
    element: '#headingWhen',
55
    on: 'top'
56
  },
57
  title: 'When do you want to meet?',
58
  text: `Suggest one or more dates and times for your ${title}. With more than one, your participants can help you choose. <p>Click the + button to add them.</p>`
59
});
60
steps.push({
61
  attachTo: {
62
    element: '#headingWhere',
63
    on: 'top'
64
  },
65
  title: 'Where do you want to meet?',
66
  text: `Suggest one or more places for your ${title}. With multiple places, your participants can help you choose. <p>We use Google Places to simplify adding them. Click the + button to begin.</p>`
67
});
68
steps.push({
69
  attachTo: {
70
    element: '.virtualThing',
71
    on: 'top'
72
  },
73
  title: 'Is this a virtual meeting?',
74
  text: `Switch between <em>in person</em> and <em>virtual</em> ${title}s such as phone calls or online conferences.`
75
});
76
steps.push({
77
  attachTo: {
78
    element: '#actionSend',
79
    on: 'top'
80
  },
81
  title: 'Sending invitations',
82
  text: `Scheduling is collaborative. After you add times and places, you can <strong>Invite</strong> participants to select their favorites. <em>A place isn't necessary for virtual ${title}s.</em>`
83
});
84
steps.push({
85
  attachTo: {
86
    element: '#actionFinalize',
87
    on: 'right'
88
  },
89
  title: 'Finalizing the plan',
90
  text: `Once you choose a time and place, you can <strong>Complete</strong> the plan. We'll email the invitations and setup reminders.`
91
92
93
});
94
steps.push({
95
  attachTo: {
96
    element: '#tourDiscussion',
97
    on: 'left'
98
  },
99
  title: 'Share messages with participants',
100
  text: 'You can write back and forth with participants on the <strong>Messages</strong> tab. <p>Messages are delivered via email.</p>'
101
});
102
steps.push({
103
  attachTo: {
104
    element: '.container',
105
    on: 'top'
106
  },
107
  title: 'Ask a question',
108
  text: `Need help? <a href="${$('#url_prefix').val()}/ticket/create">Ask a question</a> and we'll respond as quickly as we can. <p>If you prefer, you can <a href="${$('#url_prefix').val()}/user-setting?tab=guide">turn off the guide</a> in settings.</p>`
109
});

Each object element that I added to the array includes three crucial pieces of information:

  1. the CSS visual element this step points to and where it should be attached to that element. e.g. {element: '.nav-tabs', on:'top'}
  2. text for the header in the title key, e.g. 'When do you want to meet?'
  3. text for the instruction in the text key

Maintaining this array was much simpler for me than defining buttons for every single step of the tutorial. However, this meant that I needed to programmatically define buttons as I loaded the steps into the tour.

I wrote this code to add and respond to buttons for the tour properly. With each step, it creates a buttons array, which I would otherwise have to define manually:

1
for (let i = 0; i < steps.length; i++) {
2
    
3
    let buttons=[];
4
    
5
    // no back button at the start 

6
    if (i>0) {
7
        buttons.push({
8
          text: 'Back',
9
          classes: 'shepherd-button-secondary',
10
          action: function() {
11
            return tour.back();
12
          }
13
        });
14
    }
15
    
16
    // no next button on last step 

17
    if (i!=(steps.length-1)) {
18
        buttons.push({
19
          text: 'Next',
20
          classes: 'shepherd-button-primary',
21
          action: function() {
22
            return tour.next();
23
          }
24
        });
25
    } else {
26
        buttons.push({
27
          text: 'Close',
28
          classes: 'shepherd-button-primary',
29
          action: function() {
30
            return tour.hide();
31
          }
32
        });
33
    }

For example, the first step has no Back button, and the last step has no Next button. But the last step does have a Close button.

Then, each step from my array and each button array is added to the tour.

1
    tour.addStep(`step_${i}`, {
2
        text: steps[i].text,
3
        title: steps[i].title,
4
        attachTo: steps[i].attachTo,
5
        classes: 'shepherd shepherd-open shepherd-theme-arrows shepherd-transparent-text',
6
        buttons: buttons,
7
    });
8
}

Using this approach, I didn't have to repeatedly redefine the same buttons for every step of my tutorial. It also offers some programmatic ability to customize the tour dynamically for the future.

Using Yii, my chosen PHP programming framework, I added necessary include files to my asset file. This gets loaded on particular pages where the tour is needed. In my case, the meeting scheduling page:

1
<?php
2
namespace frontend\assets;
3
use yii\web\AssetBundle;
4
class MeetingAsset extends AssetBundle
5
{
6
    public $basePath = '@webroot';
7
    public $baseUrl = '@web';
8
    public $css = [
9
      ...
10
      'css/shepherd-theme-arrows.css',      
11
    ];
12
    public $js = [
13
      'js/meeting.js',
14
      ...
15
      'js/tether.min.js',
16
      'js/shepherd.min.js',
17
      'js/meeting_tour.js',
18
    ];
19
    ...

You'll see above the CSS for the Shepherd theme and the JavaScript for Tether, Shepherd, and my tour definition file, meeting_tour.js.

I also added CSS to control the overall width of my tour popup to 40% of the viewport:

1
.shepherd-element.shepherd-theme-arrows {
2
  max-width: 40%;
3
}

You can watch the example tour video above or on Vimeo. If you'd like to try it yourself, sign up at Meeting Planner and you'll be immediately taken to the scheduling tour.

Other Things to Consider

Turning Off the Visual Tour

I created a user setting for people to quickly turn off the tour. Rather than include a distracting off button on every step, I added a link to turn off the guide on the first and last steps of the tour:

Turning it off happens interactively via AJAX and displays a helpful link to the settings page below. This helps new users easily find how to turn the tour back on:

Shepherd's Advanced Capabilities

I've just been showing you the basics of Shepherd and how to integrate it quickly into your web application. So far, it's worked well for me aside from the occasional problems with arrows. However, Shepherd offers a lot more than I've reviewed, specifically around event processing and management. This allows you to adapt your tour to your application and the user's current state in a more customized way. They have very good documentation too.

For example, if a user jumps to some area of your web page, you can have the event automatically trigger a jump to another step of the tour. I might dive into this in a future tutorial.

In Closing

I hope you've enjoyed learning about Shepherd. It certainly is a visually polished, developer-friendly visual tour that you can quickly integrate into any application.

This post has been updated with contributions from Monty Shokeen. Monty is a full-stack developer who also loves to write tutorials and learn about new JavaScript libraries.

Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.