Ever heard of tours in product UI?
Product tours are self-explaining tips UI for the website user to break down complex UX and make it easily useable.
Product tours play a vital role in B2B product UI. It helps save customer support time related to repeated ‘how-to-use’ questions about the UX.
Product tours help with onboarding users to new and complex UX and helps to get users familiar with UI functionalities. They’re also useful for showcasing new updates on product UI, and they can save time for the customer success team.
Slack, Trello, Asana, and Invision are some of the big products that use product tours for different UX needs.
The indirect alternative to product tours including FAQs about product functionalities, product video demos and tours, and on-demand tips UI.
However, video tours or FAQs don’t have the same level of impact as inline product tours.
The majority of users don’t look for tutorials outside the UI.
On the other hand, on-demand tips UI are similar to product tours and can have a similar impact.
In this post, you’ll learn how to build a simple product tour for your React application. Before building, it you’ll first need to learn about existing React libraries.
Even though product tours are used by lot of companies, there aren’t many React-based tour UIs. Some of the libraries are React Tour and React Joyride.
React Tour has around 1.4k stars on Github and is moderately active.
It has very nice UI if you need a simple product tour without much customization. If this is the case, React Tour UI will be good enough.
You can view the demo for React Tour here.
With React Tour, you pass the classname selector and content for each step to the component.
It will render the tour UI based on a button click, or after mounting the component. It’s simple for static pages and UI:
const steps = [ { selector: '.tour-first-step', content: 'This is the first Step', }, { selector: '.tour-second-step', content: 'This is the second Step', } // ... ]
However, if you need to customize for a custom behavior, then it won’t work very well. The component is very rigid, and styles aren’t exposed well enough to make it reusable.
One drawback is that if you don’t use styled-components
in your project, then you won’t have any luck using the component. There is no other way — the library has a hard dependency for styled components.
Additionally, if a classname selector is not present in the current screen, then React Tour just displays the non-matched content in the center of the screen. There is no way to hide it.
The only way to overwrite such behavior is to trigger the next steps through our own logic, but that defeats the purpose of the component.
It’s almost as complex as writing your own component for product tours.
React Tour really shines when you don’t want to customize a lot, and when you want basic tour functionality with beautiful UI and UX.
It also works well for static content or dynamic content where the selector labels always exist on the UI.
The next famous React product tour library is React Joyride. It has 3k stars on Github and is also actively developed.
The UI isn’t as elegant as React Tours, but the API is less rigid. It allows for some level of customization.
Of course, it has its own limitations.
The docs aren’t good enough if you need custom solution on top of basic React tour functionality. The props API also isn’t very intuitive or simple.
The only difference is that it has solutions for most use cases in product tours. They expose all the events and actions to the end user, so you can capture those actions and do whatever customization you want.
First, let’s build a simple React tour without any custom functionality.
We’ll use react-dashboard
by creative tim as our base application.
This loads the product tour on top of it.
This is what the dashboard UI looks like:
We’ll do a product tour on this UI. You can see the final product tours UI here.
Let’s create the simple product tour component:
// tour.js import React from "react"; import JoyRide from "react-joyride"; // Tour steps const TOUR_STEPS = [ { target: ".tour-search", content: "This is where you can search the dashboard." }, { target: ".tour-orders", content: "Bar chart for total order. You can see beautiful graphs here, thanks to creative tim for such UI." }, { target: ".tour-external-links", content: "This is where you can find the external links." }, { target: ".tour-footer", content: "This is where you can see the footer links." } ]; // Tour component const Tour = () => { return ( <> <JoyRide steps={TOUR_STEPS} continuous={true} /> </> ); }; export default Tour;
Load this tour component anywhere on the page to load the blinking beacon
UI. If you click that beacon, it will open the tour. The next button will let you navigate til the end of the tour.
Joyride components take a lot of props. The most important ones are steps
props. It accepts an array of objects with target selector elements and content.
Continuous
props are used for showing the next button on each step.
You can see the demo for this simple tour component here.
Now let’s add more features and make our product tour more customized. Simple features are:
Then we’ll add the custom feature like:
Most of the basic functionalities can be achieved through the props
provided by Joyride docs.
Adding showSkipButton
to true will do the trick. Skip link will skip the remaining step on the tour.
const Tour = () => { return ( <> <JoyRide steps={TOUR_STEPS} continuous={true} showSkipButton={true} /> </> ); };
Let’s change the last button text as end tour
and skip button text to close tour
.
const Tour = () => { return ( <> <JoyRide steps={TOUR_STEPS} continuous={true} showSkipButton={true} locale={{ last: "End tour", skip: "Close tour" }} /> </> ); };
*showSkipButton*
propshideBackButton
continuous
propsUnlike other props, continuous
props work differently. They either show the Next button or show a Close button, depending on the boolean value passed to the props.
You can see how inconsistent the props API naming are. It isn’t very easy to find lot of hidden features unless you read the complete docs for Joyride couple of times 😅
Styles are exposed as an object. So if you pass a style object to the component, the component will merge it with their default styles.
const Tour = () => { return ( <> <JoyRide steps={TOUR_STEPS} continuous={true} showSkipButton={true} styles={{ tooltipContainer: { textAlign: "left" }, buttonNext: { backgroundColor: "green" }, buttonBack: { marginRight: 10 } }} locale={{ last: "End tour", skip: "Close tour" }} /> </> ); };
A caveat to this way of styling is that it only supports a handful of the object styles, which are already defined on the component.
It won’t allow you to customize everything on an element level. Also, the classnames used in the rendered elements are not very easy to customize.
However, the library exposes props to use your own elements instead of the default elements.
Some of the components are:
beaconComponent
prop)tooltipComponent
prop)So far, you’ve learned how to use the Joyride library to create a basic product tour and customize it using props.
You’ve also seen some of the limitations to styling the component.
Until now, the tour has been controlled in the library. You just pass the steps and tweak some props.
It’s possible to control the tour and trigger goto
a particular step directly through button click, but it requires some coding.
We’ll see how to do it by achieving a few of the features.
The Joyride component exposes some of the actions and events through callback. You need to capture the callback and, based on the function, you can customize your functionality.
It’s simple to make the component controlled by passing a prop stepIndex
.
stepIndex
is the index number and starts from 0. Once you pass the values, the Next and Back button clicks need to be handled by you.
Let’s get to it. First, we will define the steps:
const TOUR_STEPS = [ { target: ".tour-search", content: "This is where you can search the dashboard.", disableBeacon: true // This makes the tour to start automatically without click }, { target: ".tour-orders", content: "Bar chart for total order. You can see beautiful graphs here, thanks to creative tim for such UI." }, { target: ".tour-external-links", content: "This is where you can find the external links." }, { target: ".tour-footer", content: "This is where you can see the footer links." }, { target: ".tour-link", content: "This is where you can start the tour again in future." } ];
Here’s the initial state to make the component controlled:
// Initial state for the tour component const INITIAL_STATE = { key: new Date(), // This field makes the tour to re-render when we restart the tour run: false, continuous: true, // Show next button loading: false, stepIndex: 0, // Make the component controlled steps: TOUR_STEPS };
To auto start the tour, you need to pass disableBeacon: true
in the first step. This will just disable the beacon. But you need to trigger start by changing the state run: true
:
// Reducer will manage updating the local state const reducer = (state = INITIAL_STATE, action) => { // TODO: Implement reducer }; // Tour component const Tour = () => { // Tour state is the state which control the JoyRide component const [tourState, dispatch] = useReducer(reducer, INITIAL_STATE); useEffect(() => { // TODO: Auto start the tour }, []); const callback = data => { const { action, index, type, status } = data; // TODO: Capture close, skip, next / prev action }; const startTour = () => { // TODO: Start the tour manually }; return ( <> <JoyRide {...tourState} // Callback will pass all the actions callback={callback} showSkipButton={true} /> </> ); };
The actions that are important to make the functionality are Close button click, Skip button click, Next, and Back button click.
Let’s implement the reducer function:
// Reducer will manage updating the local state const reducer = (state = INITIAL_STATE, action) => { switch (action.type) { // start the tour case "START": return { ...state, run: true }; // Reset to 0th step case "RESET": return { ...state, stepIndex: 0 }; // Stop the tour case "STOP": return { ...state, run: false }; // Update the steps for next / back button click case "NEXT_OR_PREV": return { ...state, ...action.payload }; // Restart the tour - reset go to 1st step, restart create new tour case "RESTART": return { ...state, stepIndex: 0, run: true, loading: false, key: new Date() }; default: return state; } };
Now we’ll listen to the events and dispatch proper state changes to manage the tour:
import JoyRide, { ACTIONS, EVENTS, STATUS } from "react-joyride"; // Listen to callback and dispatch state changes const callback = data => { const { action, index, type, status } = data; if ( // If close button clicked then close the tour action === ACTIONS.CLOSE || // If skipped or end tour, then close the tour (status === STATUS.SKIPPED && tourState.run) || status === STATUS.FINISHED ) { dispatch({ type: "STOP" }); } else if (type === EVENTS.STEP_AFTER || type === EVENTS.TARGET_NOT_FOUND) { // Check whether next or back button click and update the step dispatch({ type: "NEXT_OR_PREV", payload: { stepIndex: index + (action === ACTIONS.PREV ? -1 : 1) } }); } };
Here’s a quick overview of how each action, event, and state update works:
If the Close button, Skip button, or End Tour button are clicked, then STOP the tour. Meanwhile, if the Next or Back button are clicked, then check whether the target element is present in the page.
If the target element is present, then go to that step. If it’s not present, find the next step target and iterate.
Joyride expose EVENTS, STATUS, and ACTION labels. You can use those to listen to the callback event without hardcoding it.
Let’s also auto start the tour when the page loads:
useEffect(() => { // Auto start the tour if the tour is not viewed before dispatch({ type: "START" }); }, []);
You can even trigger the start of tour using button click:
// Call startTour to start the tour const startTour = () => { // Start the tour manually dispatch({ type: "RESTART" }); };
Right now, we have it set up so that the tour will be shown every time you refresh the page.
If you only want to show the tour once and then trigger it only through manual click, you can do so using localStorage
.
You can find the working example code here and the demo here.
We’ve achieved the product tour using the Joyride library.
But what if we need to create our own?
Let’s walk through building a tour component.
The biggest challenges to building tour components include finding the target element and showing a popover component, as well as ensuring the popover component calculates the available window space and automatically displays by the target element.
It can also be difficult to ensure the tour component is reusable and that styles are easily extended.
To build a custom tour component in React, it’s easiest to isolate the functionality and component UI with these React Hooks:
useTour
– a custom Hook to build your own UI on top of functionalityTour
– a dumb UI component that consumes useTour
to load the tour portal UIThis mock code shows how useTour
works:
/* targetPosition - top, left position of the target element with respect to window gotoIndex - function that accepts stepNumber endTour - function to end tour restartTour - function to restart tour nextStep - function to update the state tonext step prevStep - function to update the state to previous step */ const { targetPosition, gotoIndex, currentStep, endTour, restartTour, nextStep, prevStep } = useTour({ steps, // ... props })
I hope this article helped you learn the tricks of creating product tour components in your React application. Let me know your experience on tour UX in the comments 🤗
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowSimplify component interaction and dynamic theming in Vue 3 with defineExpose and for better control and flexibility.
Explore how to integrate TypeScript into a Node.js and Express application, leveraging ts-node, nodemon, and TypeScript path aliases.
es-toolkit is a lightweight, efficient JavaScript utility library, ideal as a modern Lodash alternative for smaller bundles.
The use cases for the ResizeObserver API may not be immediately obvious, so let’s take a look at a few practical examples.
One Reply to "Complete guide to building product tours on your React apps"
Nice nice! I just found another lib “tour-navigator” which is highly customisable and tons of feature. there’s lib built on top of it “multiroute-tour-navigator” which allows tour through multiple routes. Really amazing