A checklist is a small “Get started” widget that sits in the corner of your app. Each item is one thing you want new users to do. An item can start a tour, open a link or run your own code, and it ticks itself off once the user has done it.
Checklists ship as their own entry. Items that start tours use the tour styles, so import driver.css as well:
import { checklist } from "driver.js/checklist";
import "driver.js/dist/driver.css";
import "driver.js/dist/checklist.css";Try it below. Finish the tour to tick its item; close it early and the item stays open. Clicking Connect GitHub in the mock dashboard ticks the second item.
Here is the code for the example above:
const onboarding = checklist({
id: "onboarding",
title: "Get started",
description: "Four steps to your first project.",
items: [
{
id: "tour",
title: "Take the tour",
tour: [
{ element: "#new-project", popover: { title: "New Project", description: "..." } },
{ element: "#connect-github", popover: { title: "Connect GitHub", description: "..." } },
],
},
{
id: "github",
title: "Connect GitHub",
completeWhen: () => user.githubConnected,
},
{
id: "invite",
title: "Invite a teammate",
action: () => openInviteModal(),
},
{
id: "docs",
title: "Read about saving progress",
href: "/docs/checklists#saving-progress",
},
],
});
onboarding.show();Every item needs an id. Saved progress is a list of these ids, so pick ids that stay the same when you reword, translate or reorder items. A warning is logged for missing or duplicate ids.
What happens when an item is clicked depends on what it has:
tour: the checklist closes and the tour starts. The item is ticked only when the user finishes the tour; closing it early leaves the item open. The checklist opens again when the tour ends either way. Pass an array of steps, a full driver configuration, or a driver() instance you created yourself; its own hooks keep working.href: rendered as a link. The item is ticked on click and the link navigates as usual.action: your function runs with (item, { checklist }) and the item is ticked.completeWhen or by calling complete(id).Some tasks are done in your app, not in the checklist. Give the item a completeWhen function and the checklist ticks it as soon as it returns true. It is checked on show(), after the item’s tour or action, and whenever you call refresh():
const onboarding = checklist({
id: "onboarding",
items: [{ id: "github", title: "Connect GitHub", completeWhen: () => user.githubConnected }],
});
// After the user connects GitHub somewhere in your app
onboarding.refresh();completeWhen also takes over from the default for tours, links and actions: the item is ticked only when it returns true, not when the tour finishes or the link is clicked. You can also tick an item directly with onboarding.complete("github").
Some tasks can’t be detected, like “Watch the intro video” or “Read the guidelines”. Set allowManualComplete and the check circle becomes a button: users tick an item off themselves, or untick it if they clicked by mistake. Clicking the item’s text still runs its tour, link or action; on an item with none of these, it ticks the item too.
checklist({
id: "onboarding",
allowManualComplete: true,
items: [
{ id: "video", title: "Watch the intro video", href: "/intro" },
// Only ticked by finishing the tour, even with manual completion on.
{ id: "tour", title: "Take the tour", tour: steps, allowManualComplete: false },
],
});It can be set per item as well, and an item’s own value wins. From code, use complete(id) and uncomplete(id).
The checklist doesn’t store anything itself. Progress comes in through completed, the ids that are already done, and goes out through onProgress on every change. Keep it wherever you like: localStorage, your API or the user record.
const onboarding = checklist({
id: "onboarding",
items,
completed: JSON.parse(localStorage.getItem("onboarding") ?? "[]"),
onProgress: ({ completed, done, total }) => {
localStorage.setItem("onboarding", JSON.stringify(completed));
},
});
onboarding.show();Since progress is plain data, your backend can tick items too: pass the ids of tasks it knows are done in completed.
Once every item is done, the checklist can show a different title, description and launcher text, for a short “well done” message. If an item is unticked, it switches back:
checklist({
id: "onboarding",
completeTitle: "You're all set! 🎉",
completeDescription: "Nice work. You're ready to go.",
completeLauncherText: "All done",
items,
});This demo starts with three of four items done; click the last one.
The checklist doesn’t hide itself when it’s done, since what happens next is up to your app. To hide it right away, call hide() from onComplete. On later visits, check the saved progress and don’t show it at all:
const onboarding = checklist({
id: "onboarding",
items,
completed: savedProgress,
onComplete: () => setTimeout(() => onboarding.hide(), 3000),
});
const { done, total } = onboarding.getProgress();
if (done < total) {
onboarding.show();
}onEvent receives everything that happens in the checklist, ready to send to your analytics:
checklist({
id: "onboarding",
items,
onEvent: event => {
// { kind: "checklist", id: "onboarding", type: "item_completed", itemId: "github", timestamp: 1760000000000 }
analytics.track(`checklist_${event.type}`, event);
},
});The event types are shown, hidden, opened, closed, item_started, item_completed, item_uncompleted, tour_ended (with the tour’s reason), completed and reset.
Pass className to add your own class to the widget, then style its parts. This is the theme used on the driver.js homepage:
checklist({
id: "onboarding",
className: "checklist-brutalist",
items,
});See Checklist Themes for its CSS and more themes to copy: dark, gradient header, a GIF banner, numbered steps, a progress ring and more.
The parts are driver-checklist-launcher, -launcher-text, -launcher-count, -panel, -header, -title, -description, -close-btn, -progress, -progress-text, -progress-bar, -progress-fill, -items, -item, -item-trigger, -item-title, -item-description and -check. Done items get driver-checklist-item-done, and the widget gets driver-checklist-complete once every item is done. The font comes from the popover’s --driver-popover-font-family variable.
To keep the default look in your brand color, set --driver-checklist-color. It colors the progress bar, the ticks and the focus rings:
:root {
--driver-checklist-color: #16a34a;
}The widget sits in the bottom-right corner by default. Use position to move it to any corner: "bottom-right", "bottom-left", "top-right" or "top-left". In the top corners, the panel opens below the launcher.
offset sets the distance in pixels from the screen edges, for example to make room for a chat bubble in the same corner:
checklist({
id: "onboarding",
position: "bottom-right",
offset: { x: 20, y: 90 },
items,
});The default is 20 pixels, or 12 on phones. You can also set it in CSS with the --driver-checklist-offset-x and --driver-checklist-offset-y variables.
On screens narrower than 480 pixels, the panel spans the width of the screen with even margins, and the launcher stays in its corner. The widget also keeps clear of the notch and the home bar on phones that have them.
Change any of the widget’s text:
checklist({
id: "onboarding",
title: "Welcome aboard 👋",
description: "Finish these to unlock your workspace.",
launcherText: "Setup guide",
progressText: "{{done}}/{{total}} complete",
launcherProgressText: "{{done}} of {{total}}",
items,
});A launcher label too long for the screen is cut off with an ellipsis, and the full text shows as a tooltip.
While a tour runs, the checklist is hidden, and it opens again when the tour ends.
Configuration passed to checklist():
const onboarding = checklist({
// Required. Identifies the checklist in events.
id: "onboarding",
// Required. Array of items, documented below.
items: [],
// Text in the panel header and on the launcher button.
title: "Get started",
description: "Four steps to your first project.",
launcherText: "Get started", // Defaults to the title
// Shown instead of the texts above once every item is done.
completeTitle: "You're all set! 🎉",
completeDescription: "Nice work. You're ready to go.",
completeLauncherText: "All done",
// Progress text; {{done}}, {{total}}, {{remaining}} and
// {{percent}} are replaced.
progressText: "{{done}} of {{total}}",
// The count on the launcher, same placeholders.
// An empty string hides it.
launcherProgressText: "{{done}}/{{total}}",
// false hides the floating launcher; open the panel from
// your own button with open() or toggle().
launcher: true,
// Corner of the screen: "bottom-right" (default),
// "bottom-left", "top-right" or "top-left".
position: "bottom-right",
// Distance in pixels from the screen edges.
// Defaults to 20, or 12 on phones.
offset: { x: 20, y: 20 },
// Extra class on the widget.
className: "my-checklist",
// Let users tick items off, or untick them, with the check circle.
allowManualComplete: false,
// Ids of items that are already done.
completed: [],
// Called with { completed, done, total } whenever progress changes.
onProgress: progress => {},
// Called when an item is ticked, and once when every item is done.
onItemComplete: (item, { checklist }) => {},
onComplete: ({ checklist }) => {},
// Called with every checklist event.
onEvent: event => {},
});Each item in the items array:
const item = {
// Required. Stable identity, used for progress.
id: "tour",
// Required. HTML is allowed, like in popovers.
title: "Take the tour",
description: "A short walk through the dashboard.",
// What clicking the item does; see Items above.
tour: [], // Steps, a driver config or a driver() instance
href: "/settings",
action: (item, { checklist }) => {},
// Ticks the item once this returns true.
completeWhen: () => false,
// Overrides the checklist's allowManualComplete for this item.
allowManualComplete: true,
// Your own data, available on the item in hooks.
data: {},
};See the API reference for the methods on the checklist.