How to Add Simple Onboarding Screens for Better UX in ReactJS
The importance, examples, and how-to guide
Table of contents
- Importance of Onboarding flows/screens
- What you'll learn
- Why are we even using React Custom Hooks in this case.
- What we will build
- Setup
- Create the Custom Hook
- Create the layout for the app
- Implement the Custom Hook
- Adding real content to modal to test the custom hook
- Let's add a nice animation as a final touch
- Just before you go
- Useful resources
Hello Friend š
I recently learned about the usefulness of onboarding flows/screens in web applications (and mobile apps too).
Importance of Onboarding flows/screens
Here is a quote from an article I found online about onboarding screens:
Onboarding ... tour/walkthrough can help you quickly show the value and know-how of your website to a visitor, and can result in:
- A low bounce rate
- Higher session durations
- Better product adoption
- Increased retention - userguiding.com
Onboarding screens are not something new because many applications we use today make use of it.
Fiverr business uses an onboarding modal to introduce new users to its SaaS as shown in the image below:
Hashnode also uses a set of onboarding screens to help users get started and create their accounts quickly. One such screen is shown below:
As you can see, onboarding screens can be a full page, a modal, or even a sidebar. The point is that whenever they are used, they serve specific purposes.
In this article, we will see how we can create a simple onboarding modal that will serve as a product tour for a demo app.
We will use ReactJS, TailwindCSS, and HeadlessUI.
Are you ready? Let's begin...
What you'll learn
- How to use React Custom Hooks to handle the logic of your app.
- How to add a lightweight transition to your React app with HeadlessUI.
Why are we even using React Custom Hooks in this case.
Simple answer: Reusability
The logic we will implement is a navigation logic and so very likely we will need to add navigation to other parts of our app UI.
Custom Hooks are perfect for reusability because they only exist locally inside a component and several other components can use them separately from each other.
Custom Hooks also eliminates the need to pass states down from parent to child as props which will cause unnecessary rerenders.
I'll leave a link to an article on Custom Hooks at the end of this article.
What we will build
A modal that has three slides. We will toggle between the slides using a Next button. See the demo below:
Setup
I created a quick guide to set up a ReactJS project with TailwindCSS added to it.
View it here
Done? Great! Let's continue.
Create the Custom Hook
Inside the src folder, create a new folder with the name hooks
. We will save any custom hook we create here.
Now create a new file inside the hooks folder with the name useNavigation.js
.
Paste this code into it:
import React, { useState } from "react"
export default function useNavigation () {
const [navIndex, setNavIndex] = useState(0)
const navHandler = (direction) => {
if(direction === "next"){
setNavIndex(currentIndex => currentIndex + 1)
}
if(direction === "previous"){
setNavIndex(currentIndex => currentIndex - 1)
}
}
return { navIndex, navHandler}
}
Explanation:
- We first import
useState
from "react".useState
is a React hook that lets us save data in state in a functional component. - Now we create a state variable
navIndex
together withsetNavIndex
, a function that lets us update/mutate the value ofnavIndex
. - We set the initial value of navIndex to
0
with this:const [navIndex, setNavIndex] = useState(0)
- Next we created
navHandler
, a handler (or a function) that handles the navigation logic using the value ofnavIndex
. navHandler
takes in an argument which we calldirection
.direction
can either be "next" or "previous" and this will determine if the navigation will go forward or backward.- The last thing to note is that we are increasing the value of
navIndex
if we pass innext
tonavHandler
and we are doing the opposite if we passprevious
.
Create the layout for the app
Go to App.js
, clear all the codes in it, and paste this:
import React from "react";
import Modal from "./components/Modal";
export default function App() {
return (
<div className="">
{/* add main app content will be here */}
<div className="">
<nav className="flex justify-between bg-slate-700 text-white py-4 px-10">
<span>Logo</span>
<span>ā</span>
</nav>
</div>
{/* Our onboarding screens will be in a modal */}
<Modal />
</div>
);
}
Explanation:
- Our app will have main contents but the contents will be behind a modal.
- The modal will hold our onboarding slides (in this case).
Let's create a Modal.js
file inside a new folder. Name the folder components
and save Modal.js
inside it.
Now add this code to Modal.js
:
import React from "react";
export default function Modal() {
return (
<div className="static">
<div className="fixed h-screen w-screen bg-black z-10 top-0 opacity-50"></div>
<div className="fixed top-0 right-0 left-0 z-20 flex justify-center">
<div className="mt-24 mx-4 my-4 bg-white w-[420px] h-[420px] overflow-hidden relative">
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6 absolute z-40 right-3 top-3 cursor-pointer"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
{/* modal content will be here */}
<div className="">
<div className="bg-slate-300 flex flex-col items-center pt-20 h-screen">
{/* content image will be here */}
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-60 w-60"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
{/* content text here */}
<h4 className="flex justify-center text-red-600 font-semibold italic text-xl pt-3">
You're all set!
</h4>
</div>
</div>
{/* modal content ends here */}
{/* navigation on line 49 */}
<div className="flex flex-col items-center py-4 absolute bottom-0 right-0 left-0 bg-white">
<div className="mb-2">1/3</div>
<button className="py-2 px-20 rounded-xl bg-slate-600 text-white">
Next
</button>
</div>
</div>
</div>
</div>
);
}
When we go back to the app, we should have this:
Cool right?
The layout for onboarding is ready.
Notice the next button at the bottom? We will use it to implement our useNavigation
custom hook.
Let's do that.
Implement the Custom Hook
To implement the custom hook, change line 49 in Modal.js
to:
{
navIndex <= 2 && (
<div className="flex flex-col items-center py-4 absolute bottom-0 right-0 left-0 bg-white">
<div className="mb-2">{navIndex + 1}/3</div>
<button
onClick={() => navHandler("next")}
className="py-2 px-20 rounded-xl bg-slate-600 text-white"
>
{navIndex === 2 ? "Done" : "Next"}
</button>
</div>
)
}
Explanation:
- We want to conditionally show the navigation and the condition is that the navigation should only show on the first 3 slides.
- We implement
useNavigation
by addingnavHandler
to the onClick event like this:onClick={() => navHandler("next")}
. This means that when the user clicks on the Next button,navHandler
will be called. Notice that navHandler has a string parameter "next". - Lastly, we use
navIndex
to decide what the text of our button will be. Either Done or Next
Don't forget to import useNavigation.js
in your Modal.js
file like this:
import React from "react";
import useNavigation from "../hooks/useNavigation"; // add this
export default function Modal() {
const { navIndex, navHandler } = useNavigation(); // and add this
...
Now in other to test if our useNavigation
logic is working, let's fill up the modal with content.
Adding real content to modal to test the custom hook
Inside the components
folder, create 4 new files:
CompOne.js:
import React from "react";
import img from "../images/1.png";
export default function CompOne() {
return (
<div className="">
<div className="bg-red-300 flex justify-center">
<img src={img} alt="bg" className="w-1/2" />
</div>
<h4 className="flex justify-center text-red-600 font-semibold italic text-xl pt-3">
Welcome to our app!
</h4>
<span className="flex justify-center">
Get on the right track towards mindfullness
</span>
</div>
);
}
CompTwo.js:
import React from "react";
import img from "../images/2.png";
export default function CompTwo() {
return (
<div className="">
<div className="bg-slate-300 flex justify-center">
<img src={img} alt="bg" className="w-1/2" />
</div>
<h4 className="flex justify-center text-red-600 font-semibold italic text-xl pt-3">
Get started with Yoga
</h4>
<span className="flex justify-center">
Easily get started in your yoga journey
</span>
</div>
);
}
CompThree.js:
import React from "react";
import img from "../images/3.png";
export default function CompThree() {
return (
<div className="">
<div className="bg-indigo-300 flex justify-center">
<img src={img} alt="bg" className="w-1/2" />
</div>
<h4 className="flex justify-center text-red-600 font-semibold italic text-xl pt-3">
Get your back
</h4>
<span className="flex justify-center">
Set up different measures to start with your life
</span>
</div>
);
}
Ready.js:
import React from "react";
export default function Ready() {
return (
<div className="">
<div className="bg-slate-300 flex flex-col items-center pt-20 h-screen">
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-60 w-60"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<h4 className="flex justify-center text-red-600 font-semibold italic text-xl pt-3">
You're all set!
</h4>
</div>
</div>
);
}
Notice that for CompOne
, CompTwo
, and CompThree
, I imported an image from a folder named images
. If you don't have this folder already, create one now and save 3 different images in it for use. Remember to use the correct name of the image when importing. In my case, I used /3.png
.
See our folder structure so far and check if it corresponds with yours:
These 4 components we just created are simply the contents for the modal so not much explanation is required for it.
Let's import them intoModal.js
like this:
import React from "react";
import CompOne from "./CompOne"; // just added
import CompTwo from "./CompTwo"; // just added
import CompThree from "./CompThree"; // just added
import Ready from "./Ready"; // just added
import useNavigation from "../hooks/useNavigation";
export default function Modal() {
const { navIndex, navHandler } = useNavigation();
// keep the rest of the code for Modal.js here
Remove all the code from {/* modal content will be here */}
to {/* modal content ends here */}
inside Modal.js
and replace it with this:
{/* modal content will be here */}
{navIndex === 0 && <CompOne />}
{navIndex === 1 && <CompTwo />}
{navIndex === 2 && <CompThree />}
{navIndex === 3 && <Ready />}
{/* modal content ends here */}
Explanation:
- We are rendering each of the 4 components we just created conditionally based on the value of
navIndex
. - So if
navIndex is 0
renderCompOne
, ifnavIndex is 1
renderCompTwo
, and so on. - This conditional rendering we are implementing will work because the value of
navIndex
changes whenever we click the next button (remember? š).
Go back to the browser and test it! š
Is yours working? Great!
Pat yourself on the back. (I just did the same to myself š)
One last thing.
Notice that the components are just appearing and disappearing when you click next? Doesn't look cool right?
Let's add a nice animation as a final touch
Pretty easy stuff.
First, head back to the terminal, stop your server and run this code below:
npm install @headlessui/react
Nice, we've installed just what we needed.
To use HeadlessUI
(what we just installed), go to Modal.js
and import it like this:
import React from "react";
import CompOne from "./CompOne";
import CompTwo from "./CompTwo";
import CompThree from "./CompThree";
import Ready from "./Ready";
import useNavigation from "../hooks/useNavigation";
import { Transition } from "@headlessui/react"; //just added
export default function Modal() {
const { navIndex, navHandler } = useNavigation();
// keep the rest of the code for Modal.js here
Now replace the code that exists between {/* modal content will be here */}
and {/* modal content ends here */}
with this code:
{/* modal content will be here */}
<Transition
show={navIndex === 0}
enter="transition ease-in-out duration-700 transform"
enterFrom="-translate-x-full"
enterTo="translate-x-0"
leave="transition ease-in-out duration-500 transform"
leaveFrom="-translate-x-0"
leaveTo="translate-x-full"
>
<CompOne />
</Transition>
<Transition
show={navIndex === 1}
enter="transition ease-in-out duration-700 transform"
enterFrom="-translate-x-full"
enterTo="translate-x-0"
leave="transition ease-in-out duration-500 transform"
leaveFrom="-translate-x-0"
leaveTo="translate-x-full"
>
<CompTwo />
</Transition>
<Transition
show={navIndex === 2}
enter="transition ease-in-out duration-700 transform"
enterFrom="-translate-x-full"
enterTo="translate-x-0"
leave="transition ease-in-out duration-500 transform"
leaveFrom="-translate-x-0"
leaveTo="translate-x-full"
>
<CompThree />
</Transition>
<Transition
show={navIndex === 3}
enter="transition ease-in-out duration-700 transform"
enterFrom="-translate-x-full"
enterTo="translate-x-0"
leave="transition ease-in-out duration-500 transform"
leaveFrom="-translate-x-0"
leaveTo="translate-x-full"
>
<Ready />
</Transition>
{/* modal content ends here */}
Now restart your server by running this on the terminal:
npm start
Head back to your browser and check the animation š
You can agree with me now that it's a bit better now right?. š
How HeadlessUI works
- It opens with the tag
<Transition>
and closes with</Transition>
. - The element we want to animate will be in between the opening and closing tag.
- Within the opening tag
<Transition>
, we specify the following things: - the condition that decides if an element will be shown or hidden inside the
show
attribute.show={navIndex === 3}
- how the element animation will be when it is being rendered (or shown or when it is entering the screen)
enter="transition ease-in-out duration-700 transform" enterFrom="-translate-x-full" enterTo="translate-x-0"
- how the element animation will be when it is being removed (or hidden or when it is leaving the screen)
And that's just it. As easy as that.enter="transition ease-in-out duration-700 transform" enterFrom="-translate-x-full" enterTo="translate-x-0"
Now please note that transition ease-in-out duration-700 transform
, -translate-x-full
and translate-x-0
are TailwindCSS utility classes.
Yes, HeadlessUI was built to use TailwindCSS classes.
I will drop the links to TailwindCSS transition and Animation classes as well as HeadlessUI documentation at the bottom of the post.
For now, that's it. We are done!
Whew! It wasn't easy writing this but I'm glad I did.
If you found it useful, please subscribe to my newsletter, follow me here on hashnode or on Twitter (I post useful stuff there too)
Just before you go
I just realized that we didn't implement the logic for closing our onboarding modal. You can do it yourself as an assignment.
Use this article I wrote here about creating a modal as reference.
Thank you and see ya later āļø