Yet another highlight example.",
}
})
```
Focus on the input below and see how the popover is shown.
Here is the code for the above example.
```js
const driverObj = driver({
popoverClass: "driverjs-theme",
stagePadding: 0,
onDestroyed: () => {
document?.activeElement?.blur();
}
});
const nameEl = document.getElementById("name");
const educationEl = document.getElementById("education");
const ageEl = document.getElementById("age");
const addressEl = document.getElementById("address");
const formEl = document.querySelector("form");
nameEl.addEventListener("focus", () => {
driverObj.highlight({
element: nameEl,
popover: {
title: "Name",
description: "Enter your name here",
},
});
});
educationEl.addEventListener("focus", () => {
driverObj.highlight({
element: educationEl,
popover: {
title: "Education",
description: "Enter your education here",
},
});
});
ageEl.addEventListener("focus", () => {
driverObj.highlight({
element: ageEl,
popover: {
title: "Age",
description: "Enter your age here",
},
});
});
addressEl.addEventListener("focus", () => {
driverObj.highlight({
element: addressEl,
popover: {
title: "Address",
description: "Enter your address here",
},
});
});
formEl.addEventListener("blur", () => {
driverObj.destroy();
});
```
---
# Theming
> Theme Driver.js to match your brand. Apply custom classes and CSS to the popover, buttons, overlay, hints and checklists, or restyle the driver from the ground up.
You can customize the look and feel of the driver by adding custom class to popover or applying CSS to different classes used by driver.js.
## Styling Popover
You can set the `popoverClass` option globally in the driver configuration or at the step level to apply custom class to the popover and then use CSS to apply styles.
```js
const driverObj = driver({
popoverClass: "my-custom-popover-class",
});
// or you can also have different classes for different steps
const driverObj2 = driver({
steps: [
{
element: "#some-element",
popover: {
title: "Title",
description: "Description",
popoverClass: "my-custom-popover-class",
},
},
],
});
```
Here is the list of classes applied to the popover which you can use in conjunction with `popoverClass` option to apply custom styles on the popover.
```css
/* Class assigned to popover wrapper */
.driver-popover {
}
/* Arrow pointing towards the highlighted element */
.driver-popover-arrow {
}
/* Rendered side and alignment of the popover. These reflect the actual
position after Driver.js flips the popover to fit it in the viewport,
so they may differ from the side/align you configured. */
.driver-popover-side-top {
}
.driver-popover-side-right {
}
.driver-popover-side-bottom {
}
.driver-popover-side-left {
}
.driver-popover-align-start {
}
.driver-popover-align-center {
}
.driver-popover-align-end {
}
/* Title and description */
.driver-popover-title {
}
.driver-popover-description {
}
/* Close button displayed on the top right corner */
.driver-popover-close-btn {
}
/* Footer of the popover displaying progress and navigation buttons */
.driver-popover-footer {
}
.driver-popover-progress-text {
}
.driver-popover-prev-btn {
}
.driver-popover-next-btn {
}
/* Shared style for the footer buttons. The built-in prev/next buttons carry it,
and you can add it to custom buttons created in onPopoverRender to give them
the default button look (or leave it off to apply your own styles). */
.driver-popover-footer-btn {
}
/* Added to the next button on the last step, when it acts as the "Done" button */
.driver-popover-next-btn.driver-popover-done-btn {
}
```
Visit the [example page](https://driverjs.com/docs/styling.md#popover) for an example that modifies the popover styles.
## Custom Font
The popover text uses the following font stack by default:
```css
"Helvetica Neue", Inter, ui-sans-serif, "Apple Color Emoji", Helvetica, Arial, sans-serif;
```
To use your own font, override the `--driver-popover-font-family` CSS variable. It's inherited, so you can set it anywhere above the popover (on `:root`, `body`, or a `popoverClass`) and it applies to the title, description, buttons, and progress text at once. [Checklists](https://driverjs.com/docs/checklists.md) read the same variable.
```css
:root {
--driver-popover-font-family: "Inter", sans-serif;
}
```
## Modifying Popover DOM
Alternatively, you can also use the `onPopoverRender` hook to modify the popover DOM before it is displayed. The hook is called with the popover DOM as the first argument.
```typescript
type PopoverDOM = {
wrapper: HTMLElement;
arrow: HTMLElement;
title: HTMLElement;
description: HTMLElement;
footer: HTMLElement;
progress: HTMLElement;
previousButton: HTMLElement;
nextButton: HTMLElement;
closeButton: HTMLElement;
footerButtons: HTMLElement;
};
onPopoverRender?: (popover: PopoverDOM, opts: { config: Config; state: State }) => void;
```
## Styling Page
Following classes are applied to the page when the driver is active.
```css
/* Applied to the `body` when the driver: */
.driver-active {
} /* is active */
.driver-fade {
} /* is animated */
.driver-simple {
} /* is not animated */
```
Following classes are applied to the overlay i.e. the lightbox displayed over the page.
```css
.driver-overlay {
}
```
## Styling Highlighted Element
Whenever an element is highlighted, the following classes are applied to it.
```css
.driver-active-element {
}
```
## Styling Hints
Hint popovers are regular Driver.js popovers, so everything above applies to them unchanged. The beacons read two CSS variables, settable globally or scoped to a class you pass through a hint's `beacon.className`:
```css
.driver-hint {
--driver-hint-size: 32px;
--driver-hint-color: #e11d48;
}
```
See [Styling Hints](https://driverjs.com/docs/hints.md#styling) for live examples, including per-beacon classes, static beacons, and the overlay.
## Styling Checklists
The checklist widget has its own stylesheet and classes. Set `--driver-checklist-color` to recolor the progress bar, the ticks and the focus rings, or pass `className` to the checklist and style its parts:
```js
checklist({
id: "onboarding",
className: "my-checklist",
items,
});
```
```css
/* Accent color, globally or scoped to your class */
.my-checklist {
--driver-checklist-color: #16a34a;
}
/* The button that opens the panel */
.my-checklist .driver-checklist-launcher {
background-color: #16a34a;
border-color: #16a34a;
}
/* The panel, its header and the progress bar */
.my-checklist .driver-checklist-panel {
border-radius: 4px;
}
.my-checklist .driver-checklist-title {
font-size: 20px;
}
.my-checklist .driver-checklist-progress-bar {
height: 6px;
}
/* Items, and items once they are done */
.my-checklist .driver-checklist-item-title {
font-weight: 500;
}
.my-checklist .driver-checklist-item-done .driver-checklist-item-title {
text-decoration: none;
}
```
See [Styling Checklists](https://driverjs.com/docs/styling.md#checklist) for live examples and the full list of class names.
---
# Migrate to 1.x
> Upgrade from Driver.js 0.x to 1.x with this step-by-step migration guide covering the new API, imports, and architecture changes in the 1.0 rewrite.
Drivers 1.x is a major release that introduces a new API and a new architecture. This page will help you migrate your code from 0.x to 1.x.
> Change in how you import the library
```diff
- import Driver from 'driver.js';
- import 'driver.js/dist/driver.min.css';
+ import { driver } from 'driver.js';
+ import "driver.js/dist/driver.css";
```
> Change in how you initialize the library
```diff
- const driverObj = new Driver(config);
- driverObj.setSteps(steps);
+ // Steps can be passed in the constructor
+ const driverObj = driver({
+ ...config,
+ steps
+ });
```
> Changes in configuration
```diff
const config = {
- overlayClickNext: false, // Option has been removed
- closeBtnText: 'Close', // Option has been removed (close button is now an icon)
- scrollIntoViewOptions: {}, // Option has been renamed
- opacity: 0.75,
+ overlayOpacity: 0.75,
- className: 'scoped-class',
+ popoverClass: 'scoped-class',
- padding: 10,
+ stagePadding: 10,
- showButtons: false,
+ showButtons: ['next', 'prev', 'close'], // pass an array of buttons to show
- keyboardControl: true,
+ allowKeyboardControl: true,
- onHighlightStarted: (Element) {},
+ onHighlightStarted?: (element?: Element, step: DriveStep, options: { config: Config; state: State }) => void;
- onHighlighted: (Element) {},
+ onHighlighted?: (element?: Element, step: DriveStep, options: { config: Config; state: State }) => void;
- onDeselected: (Element) {}, // Called when element has been deselected
+ onDeselected?: (element?: Element, step: DriveStep, options: { config: Config; state: State }) => void;
- onReset: (Element) {}, // Called when overlay is about to be cleared
+ onDestroyStarted?: (element?: Element, step: DriveStep, options: { config: Config; state: State }) => void;
+ onDestroyed?: (element?: Element, step: DriveStep, options: { config: Config; state: State }) => void;
+ onCloseClick?: (element?: Element, step: DriveStep, options: { config: Config; state: State }) => void;
- onNext: (Element) => {}, // Called when moving to next step on any step
- onPrevious: (Element) => {}, // Called when moving to next step on any step
+ // By overriding the default onNextClick and onPrevClick, you control the flow of the driver
+ // Visit for more details: https://driverjs.com/docs/configuration
+ onNextClick?: (element?: Element, step: DriveStep, options: { config: Config; state: State }) => void;
+ onPrevClick?: (element?: Element, step: DriveStep, options: { config: Config; state: State }) => void;
+ // New options added
+ overlayColor?: string;
+ stageRadius?: number;
+ popoverOffset?: number;
+ disableButtons?: ["next", "prev", "close"];
+ showProgress?: boolean;
+ progressText?: string;
+ onPopoverRender?: (popover: PopoverDOM, options: { config: Config; state: State }) => void;
}
```
> Changes in step and popover definition
```diff
const stepDefinition = {
popover: {
- closeBtnText: 'Close', // Removed, close button is an icon
- element: '.some-element', // Required
+ element: '.some-element', // Optional, if not provided, step will be shown as modal
- className: 'popover-class',
+ popoverClass: string;
- showButtons: false,
+ showButtons: ["next", "previous", "close"]; // Array of buttons to show
- title: ''; // Required
+ title: ''; // Optional
- description: ''; // Required
+ description: ''; // Optional
- // position can be left, left-center, left-bottom, top,
- // top-center, top-right, right, right-center, right-bottom,
- // bottom, bottom-center, bottom-right, mid-center
- position: 'left',
+ // Now you need to specify the side and align separately
+ side?: "top" | "right" | "bottom" | "left";
+ align?: "start" | "center" | "end";
+ // New options
+ showProgress?: boolean;
+ progressText?: string;
+ onPopoverRender?: (popover: PopoverDOM, options: { config: Config; state: State }) => void;
+ onNextClick?: (element?: Element, step: DriveStep, options: { config: Config; state: State }) => void
+ onPrevClick?: (element?: Element, step: DriveStep, options: { config: Config; state: State }) => void
+ onCloseClick?: (element?: Element, step: DriveStep, options: { config: Config; state: State }) => void
}
+ // New hook to control the flow of the driver
+ onDeselected?: (element?: Element, step: DriveStep, options: { config: Config; state: State }) => void;
+ onHighlightStarted?: (element?: Element, step: DriveStep, options: { config: Config; state: State }) => void;
+ onHighlighted?: (element?: Element, step: DriveStep, options: { config: Config; state: State }) => void;
};
```
> Changes in API methods.
```diff
- driverObj.preventMove(); // async support is built-in, no longer need to call this
- activeElement.getCalculatedPosition();
- activeElement.hidePopover();
- activeElement.showPopover();
- activeElement.getNode();
- const isActivated = driverObj.isActivated;
+ const isActivated = driverObj.isActive();
- driverObj.start(stepNumber = 0);
+ driverObj.drive(stepNumber = 0);
- driverObj.highlight(string|stepDefinition);
+ driverObj.highlight(stepDefinition)
- driverObj.reset();
+ driverObj.destroy();
- driverObj.hasHighlightedElement();
+ typeof driverObj.getActiveElement() !== 'undefined';
- driverObj.getHighlightedElement();
+ driverObj.getActiveElement();
- driverObj.getLastHighlightedElement();
+ driverObj.getPreviousElement();
+ // New options added
+ driverObj.moveTo(stepIndex)
+ driverObj.getActiveStep(); // returns the configured step definition
+ driverObj.getPreviousStep(); // returns the previous step definition
+ driverObj.isLastStep();
+ driverObj.isFirstStep();
+ driverObj.getState();
+ driverObj.getConfig();
+ driverObj.setConfig(config);
+ driverObj.refresh();
```
Please make sure to visit the [documentation](https://driverjs.com/docs/configuration) for more details.
---
# Popover
> Position Driver.js popovers with the side and align options, choose which buttons they show, change the button text, and add your own buttons and click handlers.
Every step and highlight shows a popover next to its element. This page covers where the popover sits and which buttons it shows. See [Styling](https://driverjs.com/docs/styling.md) to change how it looks.
## Position
You can control the popover position using the `side` and `align` options. The `side` option controls which side of the element the popover is shown on (`top`, `right`, `bottom`, or `left`, defaulting to `bottom`). The `align` option controls how the popover is aligned along that side: `start` lines its leading edge up with the element, `center` centers it, and `end` lines up the trailing edges (defaults to `start`).
> **Note:** Popover is intelligent enough to adjust itself to fit in the viewport. So, if you set `side` to `left` and `align` to `start`, but the popover doesn't fit in the viewport, it will automatically adjust itself to fit in the viewport. Consider highlighting and scrolling the browser to the element below to see this in action.
```js
import { driver } from "driver.js";
import "driver.js/dist/driver.css";
const driverObj = driver();
driverObj.highlight({
element: "#left-start",
popover: {
title: "Animated Tour Example",
description: "Here is the code example showing animated tour. Let's walk you through it.",
side: "left",
align: "start",
},
});
```
## Buttons
You can use the `showButtons` option to choose which buttons to show in the popover. The default value is `['next', 'previous', 'close']`.
```js
import { driver } from "driver.js";
import "driver.js/dist/driver.css";
const driverObj = driver({
showButtons: [
'next',
'previous',
'close'
],
steps: [
{
element: '#first-element',
popover: {
title: 'Popover Title',
description: 'Popover Description'
}
},
{
element: '#second-element',
popover: {
title: 'Popover Title',
description: 'Popover Description'
}
}
]
});
driverObj.drive();
```
### Change Button Text
You can also change the text of buttons using `nextBtnText`, `prevBtnText` and `doneBtnText` options. Use `closeBtnLabel` to change the screen reader label of the close button.
```js
import { driver } from "driver.js";
import "driver.js/dist/driver.css";
const driverObj = driver({
nextBtnText: '—›',
prevBtnText: '‹—',
doneBtnText: '✕',
closeBtnLabel: 'Close tour',
showProgress: true,
steps: [
// ...
]
});
driverObj.drive();
```
### Event Handlers
You can use the `onNextClick`, `onPrevClick`, `onCloseClick` and `onDoneClick` callbacks to implement custom functionality when the user clicks on the buttons.
> Please note that when you configure these callbacks, the default functionality of the buttons will be disabled. You will have to implement the functionality yourself.
> `onDoneClick` runs when the done button (the next button on the last step) is clicked. When provided, it runs instead of `onNextClick` on the last step, so you can detect when the user finishes the tour. Driver.js won't tear down the tour for you here — call `driverObj.destroy()` yourself.
```js
import { driver } from "driver.js";
import "driver.js/dist/driver.css";
const driverObj = driver({
onNextClick:() => {
console.log('Next Button Clicked');
// Implement your own functionality here
driverObj.moveNext();
},
onPrevClick:() => {
console.log('Previous Button Clicked');
// Implement your own functionality here
driverObj.movePrevious();
},
onCloseClick:() => {
console.log('Close Button Clicked');
// Implement your own functionality here
driverObj.destroy();
},
onDoneClick:() => {
console.log('Done Button Clicked');
// Runs on the last step instead of onNextClick
driverObj.destroy();
},
steps: [
// ...
]
});
driverObj.drive();
```
### Custom Buttons
You can add custom buttons using `onPopoverRender` callback. This callback is called before the popover is rendered. In the following example, we are adding a custom button that takes the user to the first step.
```js
import { driver } from "driver.js";
import "driver.js/dist/driver.css";
const driverObj = driver({
// Get full control over the popover rendering.
// Here we are adding a custom button that takes
// user to the first step.
onPopoverRender: (popover, { config, state }) => {
const firstButton = document.createElement("button");
firstButton.className = "driver-popover-footer-btn";
firstButton.innerText = "Go to First";
// Add this class to give the button the default driver.js styling.
// Leave it out if you want to apply your own styles instead.
firstButton.classList.add("driver-popover-footer-btn");
popover.footerButtons.appendChild(firstButton);
firstButton.addEventListener("click", () => {
driverObj.drive(0);
});
},
steps: [
// ..
]
});
driverObj.drive();
```
---
# Styling
> Style Driver.js popovers with your own CSS class or the onPopoverRender hook, change the overlay color and opacity, and theme checklists to match your product's look.
## Popover
You can either use the default class names and override the styles or you can pass a custom class name to the `popoverClass` option either globally or per step.
Alternatively, if want to modify the Popover DOM, you can use the `onPopoverRender` callback to get the popover DOM element and do whatever you want with it before popover is rendered.
Have a look at the [theming guide](https://driverjs.com/docs/theming.md#styling-popover) for a detailed guide, including the class names to target.
### Using CSS
```js
import { driver } from "driver.js";
import "driver.js/dist/driver.css";
const driverObj = driver({
popoverClass: 'driverjs-theme'
});
driverObj.highlight({
element: '#demo-theme',
popover: {
title: 'Style However You Want',
description: 'You can use the default class names and override the styles or you can pass a custom class name to the popoverClass option either globally or per step.'
}
});
```
Here is the CSS used for the above example:
```css
.driver-popover.driverjs-theme {
background-color: #fde047;
color: #000;
}
.driver-popover.driverjs-theme .driver-popover-title {
font-size: 20px;
}
.driver-popover.driverjs-theme .driver-popover-title,
.driver-popover.driverjs-theme .driver-popover-description,
.driver-popover.driverjs-theme .driver-popover-progress-text {
color: #000;
}
.driver-popover.driverjs-theme button:not(.driver-popover-close-btn) {
flex: 1;
text-align: center;
background-color: #000;
color: #ffffff;
border: 2px solid #000;
text-shadow: none;
font-size: 14px;
padding: 5px 8px;
border-radius: 6px;
}
.driver-popover.driverjs-theme button:not(.driver-popover-close-btn):hover {
background-color: #000;
color: #ffffff;
}
.driver-popover.driverjs-theme .driver-popover-navigation-btns {
justify-content: space-between;
gap: 3px;
}
.driver-popover.driverjs-theme .driver-popover-close-btn {
color: #9b9b9b;
}
.driver-popover.driverjs-theme .driver-popover-close-btn:hover {
color: #000;
}
.driver-popover.driverjs-theme .driver-popover-arrow-side-left.driver-popover-arrow {
border-left-color: #fde047;
}
.driver-popover.driverjs-theme .driver-popover-arrow-side-right.driver-popover-arrow {
border-right-color: #fde047;
}
.driver-popover.driverjs-theme .driver-popover-arrow-side-top.driver-popover-arrow {
border-top-color: #fde047;
}
.driver-popover.driverjs-theme .driver-popover-arrow-side-bottom.driver-popover-arrow {
border-bottom-color: #fde047;
}
```
### Using Hooks
To change the popover's DOM, for example to add your own buttons, use the `onPopoverRender` hook. See [Custom Buttons](https://driverjs.com/docs/popover.md#custom-buttons) for an example.
## Overlay
You can customize the overlay opacity and color using `overlayOpacity` and `overlayColor` options to change the look of the overlay.
> **Note:** In the examples below we have used `highlight` method to highlight the elements. The same configuration applies to the tour steps as well.
### Overlay Color
Here are some driver.js examples with different overlay colors.
```js
import { driver } from "driver.js";
import "driver.js/dist/driver.css";
const driverObj = driver({
overlayColor: 'red'
});
driverObj.highlight({
popover: {
title: 'Pass any RGB Color',
description: 'Here we have set the overlay color to be red. You can pass any RGB color to overlayColor option.'
}
});
```
## Checklist
[Checklists](https://driverjs.com/docs/checklists.md) are styled with plain CSS: an accent color for
the quick win, or your own class for a full theme.
### Accent Color
Set the `--driver-checklist-color` variable to recolor the progress bar, the
ticks and the focus rings while keeping the default look:
```css
:root {
--driver-checklist-color: #16a34a;
}
```
### Custom Theme
Pass `className` to add your own class to the widget, then style its parts. The
example below is the theme used on the driver.js homepage:
```js
checklist({
id: "onboarding",
className: "checklist-brutalist",
items,
});
```
Its CSS, and more themes ready to copy, are on the
[Checklist Themes](https://driverjs.com/docs/checklist-themes.md) page.
### Class Names
These are the classes you can target:
```css
.driver-checklist {} /* The widget, positioned in a corner */
.driver-checklist-bottom-right {} /* or -bottom-left, -top-right, -top-left */
.driver-checklist-complete {} /* Added once every item is done */
.driver-checklist-launcher {} /* The button that opens the panel */
.driver-checklist-launcher-text {}
.driver-checklist-launcher-count {} /* "1/4" */
.driver-checklist-panel {}
.driver-checklist-header {}
.driver-checklist-title {}
.driver-checklist-description {}
.driver-checklist-close-btn {}
.driver-checklist-progress {}
.driver-checklist-progress-bar {}
.driver-checklist-progress-fill {}
.driver-checklist-progress-text {} /* "1 of 4" */
.driver-checklist-items {}
.driver-checklist-item {}
.driver-checklist-item-done {} /* Added to an item once it is done */
.driver-checklist-check {} /* The circle; a button with allowManualComplete */
.driver-checklist-item-trigger {} /* The item's link, button or plain row */
.driver-checklist-item-title {}
.driver-checklist-item-description {}
```
The widget also sets `--driver-checklist-progress` to the progress as a
percentage, for progress designs drawn in CSS, like the
[progress ring](https://driverjs.com/docs/checklist-themes.md#progress-ring). The font follows the
popover's `--driver-popover-font-family` variable. See the
[Checklists guide](https://driverjs.com/docs/checklists.md#customizing) for position and text options.
---
# Feature Hints
> Add pulsing hint beacons to your UI with Driver.js. Users click a beacon to open a popover explaining the feature. Style the beacons and popovers to match your product.
Hints are pulsing beacons that sit on the page. The user clicks one to open a
popover, in any order, with no overlay and nothing blocked, so the page stays
interactive.
They ship as their own entry with a self-contained stylesheet, so tour-only
users never load them and you don't need `driver.css` unless you also use tours:
```js
import { hints } from "driver.js/hints";
import "driver.js/dist/hints.css";
```
Each hint points at an element and describes it with the same popover you know
from tours. The demo below also turns on the optional overlay, which spotlights
the element while its hint is open:
Here is the code for the example above:
```js
const productHints = hints({
overlay: true,
overlayOpacity: 0.5,
hints: [
{
element: "#export-btn",
id: "export",
popover: {
title: "Export your data",
description: "Download this report as CSV or PDF.",
},
},
{
element: "#summary",
id: "summary",
beacon: { side: "left", align: "center" },
popover: {
title: "Auto-generated summary",
description: "This paragraph is written for you from the quarter's numbers.",
side: "bottom",
},
},
],
});
productHints.show();
```
## Closing vs. dismissing
The two are deliberately different:
- Closing: clicking the beacon again, clicking anywhere outside, or pressing
Escape closes the popover. The beacon stays, and the hint can be opened again.
- Dismissing: clicking the _Got it_ button removes the beacon entirely and
fires `onDismiss`. The hint is gone for the session. Provide `onButtonClick`
to take over the button and decide yourself, the same way `onNextClick`
takes over a tour's next button.
Only one hint popover is open at a time; opening another swaps it.
## Remembering dismissals
Driver.js keeps dismissals in memory for the session and stays out of the
storage business. `onDismiss` with stable `id`s is the hook, and storage is yours:
```js
const dismissed = new Set(JSON.parse(localStorage.getItem("hints") ?? "[]"));
const productHints = hints({
hints: allHints.filter(hint => !dismissed.has(hint.id)),
onDismiss: (element, hint) => {
dismissed.add(hint.id);
localStorage.setItem("hints", JSON.stringify([...dismissed]));
},
});
productHints.show();
```
## Dimming the page
Pass `overlay: true` to dim the page while a hint is open. The hint reads
exactly like a tour step: the element is cut out of the dim and stays
interactive, the popover anchors to the element rather than the beacon, and
the beacon itself steps aside while its popover is up. Everything else,
including the other beacons, sits under the overlay; clicking the dimmed page
closes the hint like any outside click:
```js
const productHints = hints({
overlay: true,
overlayColor: "#000",
overlayOpacity: 0.5,
hints: [...],
});
```
## Using hints alongside a tour
Hints and tours coexist without any wiring: while a tour is running, the
beacons hide and any open hint closes; when the tour ends, the beacons return
on their own. A common pattern is a hint whose button launches the tour,
using `onButtonClick` to take over the button:
```js
const tour = driver({ steps: [...] });
const productHints = hints({
hints: [
{
element: "#whats-new",
id: "whats-new",
popover: {
title: "New dashboard",
description: "Want a quick walkthrough?",
buttonText: "Take the tour",
onButtonClick: (element, hint, { hints: instance }) => {
instance.close();
tour.drive();
},
},
},
],
});
```
Since hint popovers are regular Driver.js popovers, `onPopoverRender` works
too when you need additional buttons or custom markup, exactly like in tours.
## Styling
Everything about a hint can be customized: the beacon's position, color, size
and animation, and the popover through the same theming that tours use.
### Beacon placement
A beacon sits on one of twelve anchor points of its element's box: a `side`
(`top`, `right`, `bottom`, `left`) plus an `align` (`start`, `center`, `end`).
The default is the top-right corner.
When an anchor point lands a little off, nudge the beacon in pixels with
`offsetX` and `offsetY`. Positive `offsetX` moves it right and negative left;
positive `offsetY` moves it down and negative up. This is handy for large or
irregular targets, such as an illustration whose focus is not at a box edge:
```js
const hint = {
element: "#dashboard",
beacon: { side: "top", align: "end", offsetX: -12, offsetY: 8 },
};
```
### Beacon color and size
Beacons read two CSS variables. Set them globally on `.driver-hint`, or give
individual beacons a `className` and scope the variables to it:
```css
/* All beacons */
.driver-hint {
--driver-hint-color: #e11d48;
--driver-hint-size: 28px;
}
/* Only beacons carrying a class */
.my-special-hint {
--driver-hint-color: #059669;
}
```
```js
const productHints = hints({
hints: [
{
element: "#export-btn",
beacon: { className: "my-special-hint" },
popover: { title: "A rose beacon", description: "..." },
},
],
});
```
### Static beacons
Set `animate: false` on a hint (or on the instance-level `beacon` defaults)
for a still dot without the pulse. Users who prefer reduced motion get the
still dot automatically.
```js
const productHints = hints({
// Applies to every hint; a hint's own beacon config wins.
beacon: { animate: false },
hints: [...],
});
```
### Custom button text
The dismiss button says _Got it_ by default. Change it for all hints with the
instance-level `buttonText`, per hint through the popover, or hide it entirely
with `showButton: false` for hints you dismiss programmatically.
```js
const productHints = hints({
buttonText: "Thanks, understood",
hints: [
{
element: "#share-btn",
popover: {
title: "No button at all",
description: "...",
showButton: false,
},
},
],
});
```
### Theming the popover
Hint popovers are regular Driver.js popovers, so `popoverClass` and the
[popover styling](https://driverjs.com/docs/styling.md#popover) techniques apply unchanged, shown here with
the same theme used across these docs:
```js
const productHints = hints({
popoverClass: "driverjs-theme",
hints: [...],
});
```
## Options
Configuration passed to `hints()`:
```js
const productHints = hints({
// Array of hints, documented below.
hints: [],
// Defaults applied to every hint's beacon; a hint's own values win.
beacon: { side: "top", align: "end", offsetX: 0, offsetY: 0, animate: true, className: "" },
// Text of the dismiss button. Defaults to "Got it".
buttonText: "Got it",
// Class and offset for the hint popovers, same meaning as in tours.
popoverClass: "my-theme",
popoverOffset: 10,
// Dim the page while a hint is open. Off by default.
overlay: false,
overlayColor: "#000",
overlayOpacity: 0.7,
// Called when a hint popover is opened / a hint is dismissed.
onOpen: (element, hint, { config, hints }) => {},
onDismiss: (element, hint, { config, hints }) => {},
// Runs instead of dismissing when the button is clicked, like a
// tour's onNextClick takes over the default advance. Call
// hints.dismiss(hint.id) yourself to also remove the hint.
onButtonClick: (element, hint, { config, hints }) => {},
});
```
Each hint in the `hints` array:
```js
const hint = {
// Selector, element, or a function returning one. A hint whose element
// is missing is skipped and picked up again on the next show().
element: "#export-btn",
// Stable identity, used by open/dismiss/restore and in the hooks.
// Defaults to the hint's index.
id: "export",
// Where the beacon sits on the element's box, and how it looks.
beacon: { side: "top", align: "end", offsetX: 0, offsetY: 0, animate: true, className: "" },
popover: {
title: "Export your data",
description: "Download this report as CSV or PDF.",
side: "bottom",
align: "start",
popoverClass: "my-theme",
// The dismiss button; hide it for popovers you dismiss programmatically.
showButton: true,
buttonText: "Got it",
// Overrides the instance-level onButtonClick for this hint.
onButtonClick: (element, hint, { config, hints }) => {},
onPopoverRender: (popover, { hint, hints }) => {},
},
// Hint-level hooks, taking precedence over the global ones.
onOpen: (element, hint, opts) => {},
onDismiss: (element, hint, opts) => {},
// Anything you want to carry along; available wherever the hint is.
data: {},
};
```
Methods on the returned instance:
```js
const productHints = hints({ ... });
productHints.show(); // mount the beacons
productHints.hide(); // remove beacons and listeners; show() brings them back
productHints.open("export"); // open a hint's popover programmatically
productHints.close(); // close the open popover, keeping its beacon
productHints.dismiss("export"); // dismiss a hint, firing onDismiss
productHints.restore("export"); // bring a dismissed hint back
productHints.restoreAll(); // bring every dismissed hint back
productHints.setHints([...]); // replace the hints; resets dismissals
productHints.getHints(); // the configured hints
productHints.getActive(); // the hint whose popover is open, if any
productHints.isVisible(); // whether the beacons are currently shown
productHints.refresh(); // reposition after layout changes
```
---
# Checklists
> Add an onboarding checklist to your app with Driver.js. Items launch product tours, follow links or wait for the user to do something, and tick themselves off as users make progress.
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:
```js
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:
```js
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();
```
## Items
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](https://driverjs.com/docs/configuration.md#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.
- None of these: the item is a plain row, ticked by `completeWhen` or by
calling `complete(id)`.
### Completing items from your app
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()`:
```js
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")`.
### Letting users tick items off
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.
```js
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)`.
## Saving progress
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.
```js
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`.
## When everything is done
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:
```js
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:
```js
const onboarding = checklist({
id: "onboarding",
items,
completed: savedProgress,
onComplete: () => setTimeout(() => onboarding.hide(), 3000),
});
const { done, total } = onboarding.getProgress();
if (done < total) {
onboarding.show();
}
```
## Tracking events
`onEvent` receives everything that happens in the checklist, ready to send to
your analytics:
```js
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`](https://driverjs.com/docs/ending-a-tour.md#finished-or-closed)),
`completed` and `reset`.
## Customizing
### Theme
Pass `className` to add your own class to the widget, then style its parts.
This is the theme used on the driver.js homepage:
```js
checklist({
id: "onboarding",
className: "checklist-brutalist",
items,
});
```
See [Checklist Themes](https://driverjs.com/docs/checklist-themes.md) 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.
### Accent color
To keep the default look in your brand color, set `--driver-checklist-color`.
It colors the progress bar, the ticks and the focus rings:
```css
:root {
--driver-checklist-color: #16a34a;
}
```
### Position
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:
```js
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.
### Text
Change any of the widget's text:
```js
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.
## Options
Configuration passed to `checklist()`:
```js
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:
```js
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](https://driverjs.com/docs/api.md#checklists) for the methods on the checklist.
---
# Checklist Themes
> Ready-made themes for Driver.js checklists: dark mode, gradient header, GIF banner, numbered steps, progress ring, cards, compact and a completion celebration. Copy the CSS and go.
Each theme below is plain CSS on top of the default
[checklist](https://driverjs.com/docs/checklists.md) styles. To use one, copy its CSS into your
stylesheet, then pass its class name to the checklist:
```js
import { checklist } from "driver.js/checklist";
import "driver.js/dist/driver.css";
import "driver.js/dist/checklist.css";
import "./checklist-theme.css"; // The CSS you copied
checklist({
id: "onboarding",
className: "checklist-gradient",
items,
}).show();
```
The themes only change how the checklist looks, so you can mix their rules or
use one as a starting point for your own. See [Styling](https://driverjs.com/docs/styling.md#class-names)
for every class name you can target.
## Gradient Header
A bold, colored header that carries your brand. The launcher turns into a
gradient pill.
```css
.driver-checklist.checklist-gradient {
--driver-checklist-color: #7c3aed;
}
.driver-checklist.checklist-gradient .driver-checklist-launcher {
border: none;
border-radius: 999px;
background: linear-gradient(135deg, #7c3aed, #db2777);
box-shadow: 0 6px 20px rgba(124, 58, 237, 0.35);
}
.driver-checklist.checklist-gradient .driver-checklist-launcher-count {
padding: 2px 8px;
border-radius: 999px;
background-color: rgba(255, 255, 255, 0.25);
color: #fff;
}
.driver-checklist.checklist-gradient .driver-checklist-panel {
border: none;
border-radius: 16px;
box-shadow: 0 12px 32px rgba(76, 29, 149, 0.2);
}
.driver-checklist.checklist-gradient .driver-checklist-header {
padding: 18px 18px 16px;
background: linear-gradient(135deg, #7c3aed, #db2777);
color: #fff;
}
.driver-checklist.checklist-gradient .driver-checklist-title {
font-weight: 700;
}
.driver-checklist.checklist-gradient .driver-checklist-description,
.driver-checklist.checklist-gradient .driver-checklist-progress-text {
color: rgba(255, 255, 255, 0.85);
}
.driver-checklist.checklist-gradient .driver-checklist-close-btn {
color: rgba(255, 255, 255, 0.7);
}
.driver-checklist.checklist-gradient .driver-checklist-close-btn:hover {
color: #fff;
}
.driver-checklist.checklist-gradient .driver-checklist-progress-bar {
background-color: rgba(255, 255, 255, 0.3);
}
.driver-checklist.checklist-gradient .driver-checklist-progress-fill {
background-color: #fff;
}
.driver-checklist.checklist-gradient .driver-checklist-items {
padding-top: 8px;
}
```
## Cards
Each item in its own card, with a chevron on the ones that do something. Done
items turn green.
```css
.driver-checklist.checklist-cards {
--driver-checklist-color: #16a34a;
}
.driver-checklist.checklist-cards .driver-checklist-items {
display: flex;
flex-direction: column;
gap: 8px;
padding: 4px 14px 14px;
}
.driver-checklist.checklist-cards .driver-checklist-item {
align-items: center;
padding: 12px;
border: 1px solid #e5e5e5;
border-radius: 10px;
}
/* A chevron on items that do something when clicked. */
.driver-checklist.checklist-cards
.driver-checklist-item:has(button.driver-checklist-item-trigger, a.driver-checklist-item-trigger)::after {
content: "";
flex-shrink: 0;
width: 7px;
height: 7px;
margin-right: 4px;
border: solid #a3a3a3;
border-width: 1.5px 1.5px 0 0;
transform: rotate(45deg);
}
.driver-checklist.checklist-cards
.driver-checklist-item:has(
button.driver-checklist-item-trigger:hover,
a.driver-checklist-item-trigger:hover,
button.driver-checklist-check + div.driver-checklist-item-trigger:hover,
.driver-checklist-item-trigger:focus-visible
) {
border-color: #d4d4d4;
background-color: #fafafa;
}
.driver-checklist.checklist-cards .driver-checklist-item-done {
border-color: #bbf7d0;
background-color: #f0fdf4;
}
.driver-checklist.checklist-cards .driver-checklist-item-done .driver-checklist-item-title {
text-decoration: none;
}
```
## Numbered Steps
Numbers instead of empty circles, for checklists meant to be done in order.
CSS counters number the items, so there is nothing to add to the items
themselves.
```css
.driver-checklist.checklist-numbered .driver-checklist-items {
counter-reset: checklist-step;
}
/* The number sits on the title's baseline, whatever the font. */
.driver-checklist.checklist-numbered .driver-checklist-item {
counter-increment: checklist-step;
align-items: baseline;
}
/* The circle shows the step number until the item is done. */
.driver-checklist.checklist-numbered .driver-checklist-check {
display: flex;
align-items: center;
justify-content: center;
width: 22px;
height: 22px;
margin-top: 0;
border-color: #d4d4d4;
font-size: 12px;
font-weight: 600;
color: #525252;
}
.driver-checklist.checklist-numbered .driver-checklist-check::after {
content: counter(checklist-step);
}
/* Kept but hidden once done, so the circle still has a baseline to align by. */
.driver-checklist.checklist-numbered .driver-checklist-item-done .driver-checklist-check::after {
color: transparent;
}
.driver-checklist.checklist-numbered .driver-checklist-item-done .driver-checklist-check {
border-color: var(--driver-checklist-color, #2d2d2d);
}
```
## Progress Ring
A ring around the launcher that fills up as items are done, with the number of
items left inside it. The checklist sets `--driver-checklist-progress` to the
progress as a percentage for the ring, `{{remaining}}` puts the count inside it,
and `{{percent}}` shows the percentage in the panel:
```js
checklist({
id: "onboarding",
className: "checklist-ring",
progressText: "{{percent}}% complete",
launcherProgressText: "{{remaining}}",
items,
});
```
```css
/* --driver-checklist-progress holds the progress as a percentage, so a
conic gradient can draw it as a ring around the launcher's count. */
.driver-checklist.checklist-ring {
--driver-checklist-color: #22c55e;
}
.driver-checklist.checklist-ring .driver-checklist-launcher {
gap: 10px;
padding: 6px 6px 6px 14px;
border-radius: 999px;
}
.driver-checklist.checklist-ring .driver-checklist-launcher-count {
display: grid;
place-items: center;
width: 32px;
height: 32px;
border-radius: 50%;
background:
radial-gradient(closest-side, #2d2d2d 78%, transparent 80% 100%),
conic-gradient(#22c55e var(--driver-checklist-progress), rgba(255, 255, 255, 0.2) 0);
font-size: 12px;
font-weight: 600;
color: #fff;
}
.driver-checklist.checklist-ring .driver-checklist-progress-bar {
height: 8px;
border-radius: 4px;
}
.driver-checklist.checklist-ring .driver-checklist-progress-text {
order: -1;
font-weight: 600;
color: #16a34a;
}
```
## Compact
A small checklist that stays out of the way, for items that don't need a
description. The launcher counts what is left with `{{remaining}}`:
```js
checklist({
id: "onboarding",
className: "checklist-compact",
title: "Setup",
launcherProgressText: "{{remaining}} left",
items, // Titles only
});
```
```css
.driver-checklist.checklist-compact .driver-checklist-launcher {
padding: 7px 12px;
border-radius: 999px;
font-size: 13px;
}
@media (min-width: 481px) {
.driver-checklist.checklist-compact .driver-checklist-panel {
width: 260px;
}
}
.driver-checklist.checklist-compact .driver-checklist-header {
padding: 12px 14px 8px;
}
.driver-checklist.checklist-compact .driver-checklist-title {
font-size: 15px;
}
.driver-checklist.checklist-compact .driver-checklist-progress {
margin-top: 8px;
}
.driver-checklist.checklist-compact .driver-checklist-items {
padding: 0 6px 6px;
}
.driver-checklist.checklist-compact .driver-checklist-item {
gap: 8px;
padding: 6px 8px;
}
.driver-checklist.checklist-compact .driver-checklist-item-title {
font-size: 13px;
font-weight: 500;
}
.driver-checklist.checklist-compact .driver-checklist-check {
width: 14px;
height: 14px;
}
```
## GIF Banner
An image, GIF or animated illustration above the title, to make the welcome
feel friendly. The description accepts HTML, so the image goes there, and the
CSS moves it above the title:
```js
checklist({
id: "onboarding",
className: "checklist-media",
title: "Welcome to Foxbase",
description: '
Four quick steps to get you going.
', items, }); ``` ```css /* The image goes in the description, as HTML. These rules lift it above the title as a banner, with the description text below. */ .driver-checklist.checklist-media .driver-checklist-header { display: flex; flex-direction: column; } .driver-checklist.checklist-media .driver-checklist-description { display: contents; } .driver-checklist.checklist-media .driver-checklist-description img { order: -1; display: block; width: 100%; height: 140px; margin-bottom: 12px; border: 1px solid #ececec; border-radius: 8px; object-fit: cover; } .driver-checklist.checklist-media .driver-checklist-description p { margin: 4px 0 0; font-size: 14px; line-height: 1.5; } .driver-checklist.checklist-media .driver-checklist-close-btn { top: 22px; right: 22px; width: 26px; height: 26px; border-radius: 50%; background-color: rgba(255, 255, 255, 0.9); font-size: 16px; line-height: 26px; color: #2d2d2d; } ``` ## Midnight A dark theme for dark apps, with a violet accent. ```css .driver-checklist.checklist-midnight { --driver-checklist-color: #a78bfa; color: #f3f4f6; } .driver-checklist.checklist-midnight .driver-checklist-launcher { border-color: #374151; background-color: #111827; color: #f9fafb; } .driver-checklist.checklist-midnight .driver-checklist-launcher:hover { background-color: #1f2937; } .driver-checklist.checklist-midnight .driver-checklist-launcher-count { color: #a78bfa; } .driver-checklist.checklist-midnight .driver-checklist-panel { border-color: #1f2937; background-color: #111827; box-shadow: 0 12px 32px rgba(0, 0, 0, 0.35); } .driver-checklist.checklist-midnight .driver-checklist-description, .driver-checklist.checklist-midnight .driver-checklist-progress-text, .driver-checklist.checklist-midnight .driver-checklist-item-description { color: #9ca3af; } .driver-checklist.checklist-midnight .driver-checklist-close-btn { color: #6b7280; } .driver-checklist.checklist-midnight .driver-checklist-close-btn:hover { color: #f9fafb; } .driver-checklist.checklist-midnight .driver-checklist-progress-bar { background-color: #1f2937; } .driver-checklist.checklist-midnight .driver-checklist-check { border-color: #4b5563; } .driver-checklist.checklist-midnight .driver-checklist-item-done .driver-checklist-check { border-color: #a78bfa; } .driver-checklist.checklist-midnight .driver-checklist-item-done .driver-checklist-item-title { color: #6b7280; } .driver-checklist.checklist-midnight .driver-checklist-item:has( button.driver-checklist-item-trigger:hover, a.driver-checklist-item-trigger:hover, button.driver-checklist-check + div.driver-checklist-item-trigger:hover, .driver-checklist-item-trigger:focus-visible ) { background-color: #1f2937; } ``` ## Celebration A finished state that rewards the user. Once every item is done, the checklist shows `completeTitle` and `completeDescription`, the launcher shows `completeLauncherText`, and the CSS turns it green. This demo starts with three of four items done, so click the last one to see it. ```js checklist({ id: "onboarding", className: "checklist-celebration", completeTitle: "You're all set! 🎉", completeDescription: "Nice work. You're ready to go.", completeLauncherText: "All done", items, }); ``` ```css /* The widget gets .driver-checklist-complete once every item is done. */ .driver-checklist.checklist-celebration.driver-checklist-complete { --driver-checklist-color: #16a34a; } .driver-checklist.checklist-celebration.driver-checklist-complete .driver-checklist-launcher { border-color: #16a34a; background-color: #16a34a; } .driver-checklist.checklist-celebration.driver-checklist-complete .driver-checklist-header { background-color: #f0fdf4; } .driver-checklist.checklist-celebration.driver-checklist-complete .driver-checklist-title { font-weight: 600; color: #15803d; animation: checklist-celebration-pop 400ms ease-out; } @keyframes checklist-celebration-pop { from { opacity: 0; transform: scale(0.95); } } ``` For confetti or a "You did it" modal, use the `onComplete` hook. It fires once when the last item is done: ```js checklist({ id: "onboarding", items, onComplete: () => confetti(), }); ``` ## Brutalist The theme of the driver.js homepage: thick borders, hard shadows and a yellow header. ```css /* The theme of the driver.js homepage. */ .driver-checklist.checklist-brutalist { --driver-checklist-color: #000; } .driver-checklist.checklist-brutalist .driver-checklist-launcher { padding: 10px 16px; border: 2px solid #000; border-radius: 12px; background-color: #fde047; color: #000; box-shadow: 3px 3px 0 0 #000; } .driver-checklist.checklist-brutalist .driver-checklist-launcher:hover { background-color: #facc15; } .driver-checklist.checklist-brutalist .driver-checklist-launcher:active { transform: translate(2px, 2px); box-shadow: 1px 1px 0 0 #000; } .driver-checklist.checklist-brutalist .driver-checklist-launcher-count { padding: 2px 7px; border-radius: 999px; background-color: #000; color: #fde047; } .driver-checklist.checklist-brutalist .driver-checklist-panel { border: 2px solid #000; border-radius: 12px; box-shadow: 4px 4px 0 0 #000; } .driver-checklist.checklist-brutalist .driver-checklist-header { border-bottom: 2px solid #000; background-color: #fde047; } .driver-checklist.checklist-brutalist .driver-checklist-title { font-weight: 700; color: #000; } .driver-checklist.checklist-brutalist .driver-checklist-items { padding-top: 7px; } .driver-checklist.checklist-brutalist .driver-checklist-description, .driver-checklist.checklist-brutalist .driver-checklist-progress-text, .driver-checklist.checklist-brutalist .driver-checklist-close-btn { color: rgba(0, 0, 0, 0.7); } .driver-checklist.checklist-brutalist .driver-checklist-progress-bar { height: 10px; border: 2px solid #000; background-color: #fff; } .driver-checklist.checklist-brutalist .driver-checklist-progress-fill { border-radius: 0; } .driver-checklist.checklist-brutalist .driver-checklist-check { border-color: #000; } .driver-checklist.checklist-brutalist .driver-checklist-item:has( button.driver-checklist-item-trigger:hover, a.driver-checklist-item-trigger:hover, button.driver-checklist-check + div.driver-checklist-item-trigger:hover, .driver-checklist-item-trigger:focus-visible ) { background-color: #fef9c3; } ```