Create stunning product tours with driver.js

Created by human

Product tours

Sometimes, when you are rolling new features to your customers, it's nice to make a quick showcase of where these new features are located in the UI and how to unlock their full potential. There are many ways of announcing new features like changelogs, dedicated pages or special icons but for me, one of the best solutions is to guide users through these new UI changes so it will be really clear and intuitive. Also by leveraging analytics tools, you can monitor how many users see these features. This technique is called "product tour" and in basic version it can look like this:

Product tour example

Okay, but which library should I use for that?

Driver.js

There are many libraries for creating product tours but in my opinion, Driver.js stands out. Why?

  • it's the most simple solutions
  • it's super intuitive
  • it has no external depenencies depencencies
  • it has full TS support
  • it's 100% customizable

The API is so good, that you can add multi-step product tours in a few lines of code. Let's see how we can make it in Vue.js (the framework here doesn't matter, the implementations should be identical in other frameworks or pure JS).

Adding product tour to our page

First of all, we have to install driver.js:

npm install driver.js

Now, let's create some product tour steps for my website. On my homepage, I have a theme switcher at the top right corner:

Theme switcher

This element has a class named .theme-switcher.

Also, I have a searchbar where you can filter the articles on the homepage:

Search bar

This element has a class named .search-input.

Now I want to create a two-step product tour that will highlight those two elements and show some useful descriptions.

Because all those elements are on a single page (homepage) we can add driver.js-related logic to our homepage component. Let's import the driver js script as well as the CSS file:

// pages/index.vue
<script setup lang=ts>
import { driver } from 'driver.js';
import 'driver.js/dist/driver.css';
</script>

Now we have to find a place where we can initialize driver.js. OnMounted hook would be perfect for that:

// pages/index.vue
<script setup lang=ts>
import { driver } from 'driver.js';
import 'driver.js/dist/driver.css';

function driverJsInit () {
    // ...
}

onMounted(() => {
    driverJsInit();
});
</script>

We have created a new function driverJsInit and called it from inside of onMounted hook. Now let's see how easy it is to add product tours for the 2 elements mentioned above:

// pages/index.vue
<script setup lang=ts>
import { driver } from 'driver.js';
import 'driver.js/dist/driver.css';

function driverJsInit () {
    const driverObj = driver({
        showProgress: true,
        steps: [
            {
                element: '.theme-switcher',
                popover: {
                    title: 'Theme switcher',
                    description: 'Here you can toggle between dark and light mode',
                    side: 'left',
                    align: 'start'
                }
            },
            {
                element: '.search-input',
                popover: {
                    title: 'Searchbar',
                    description: 'Here you can search for interesting article',
                    side: 'left',
                    align: 'start'
                }
            }
        ],
    });

    driverObj.drive();
}

onMounted(() => {
    driverJsInit();
});
</script>

Even if the API is pretty self-explanatory let's take a look at how it works. First of all, you have to pass a config object to a driver.js function:

// pages/index.vue
<script setup lang=ts>
function driverJsInit () {
    const driverObj = driver({
        // config
    });

    driverObj.drive();
}
</script>

and run the tour by calling drive() on the driver object.

You can find more about the configuration here.

Inside the configuration object, we have to set up our steps. The structure looks like this:

steps: [
    {
        element: '.theme-switcher',
        popover: {
            title: 'Theme switcher',
            description: 'Here you can toggle between dark and light mode',
            side: 'left',
            align: 'start'
        }
    },
    {
        element: '.search-input',
        popover: {
            title: 'Searchbar',
            description: 'Here you can search for interesting article',
            side: 'left',
            align: 'start'
        }
    }
]

So it's an array of steps where each step has to be connected to a single dom element. We can target this element by using the element property by assigning a selector in the form of a string. I want to target elements with classes .theme-switcher and .search-input. Next, inside popover property, you can set everything connected with the popover like title, description, side and align.

Also, there are more global settings like showProgress which let you decide if you want to show tour progress inside the popover.

Let's refresh the page and see what it looks like:

Result

Cool! As you can see there is a nice popover with the title and description we previously set and you can navigate to another step by using the next button.

Showing product tour only once

Great, we have our product tour up and running. But if you refresh the page you will see it every single time and that's something we don't want. The simplest solution would be setting some localStorage info storing information about the fact that user have seen the full product tour. How do we know about that? There is a special event provided by driver.js called onDestroyed. We just have to add it to our configuration object and set a flag in localStorage:

onDestroyed: () => {
    localStorage.setItem('product_tour_seen', 'true');
}

and before initializing our driver js we can check if it's necessary to show the product tour:

const productTourSeen = localStorage.getItem('product_tour_seen') === 'true';

if (productTourSeen) { return; }

Full code

<script setup lang="ts">
import { driver } from 'driver.js';
import 'driver.js/dist/driver.css';
useHead({
    title: 'Blog'
});

onMounted(() => {
    driverJsInit();
});

function driverJsInit () {
    const productTourSeen = localStorage.getItem('product_tour_seen') === 'true';

    if (productTourSeen) { return; }

    const driverObj = driver({
        showProgress: true,
        steps: [
            {
                element: '.theme-switcher',
                popover: {
                    title: 'Theme switcher',
                    description: 'Here you can toggle between dark and light mode',
                    side: 'left',
                    align: 'start'
                }
            },
            {
                element: '.search-input',
                popover: {
                    title: 'Searchbar',
                    description: 'Here you can search for interesting article',
                    side: 'left',
                    align: 'start'
                }
            }
        ],
        onDestroyed: () => {
            localStorage.setItem('product_tour_seen', 'true');
        }
    });

    driverObj.drive();
}
</script>

Summary

Driver.js is a cool library. I love how simple it is. You look at docs, spend a few minutes and know exactly how should you use this library. And it has no dependencies! It will definitely become my go-to solution for product tours.