# Installation > Install Driver.js with npm, yarn, pnpm, or a CDN script tag and add the lightweight, zero-dependency JavaScript tour library to your project in seconds. Run one of the following commands to install the package: ```bash # Using npm npm install driver.js # Using pnpm pnpm install driver.js # Using yarn yarn add driver.js ``` Alternatively, you can use CDN and include the script in your HTML file: ```html ``` ## Start Using Once installed, you can import the package in your project. The following example shows how to highlight an element: ```js import { driver } from "driver.js"; import "driver.js/dist/driver.css"; const driverObj = driver(); driverObj.highlight({ element: "#some-element", popover: { title: "Title", description: "Description", }, }); ``` [Hints](https://driverjs.com/docs/hints.md) ship as their own entry with a self-contained stylesheet, so import them only when you use them: ```js import { hints } from "driver.js/hints"; import "driver.js/dist/hints.css"; ``` [Checklists](https://driverjs.com/docs/checklists.md) work the same way. Their items run tours, so keep `driver.css` alongside: ```js import { checklist } from "driver.js/checklist"; import "driver.js/dist/checklist.css"; ``` ### Note on CDN If you are using the CDN, you will have to use the package from the `window` object: ```js const driver = window.driver.js.driver; const driverObj = driver(); driverObj.highlight({ element: "#some-element", popover: { title: "Title", description: "Description", }, }); ``` The hints build exposes its own global: ```js const hints = window.driverHints.hints; ``` And so does the checklist build. Load it after the main script, which it uses to run tours: ```js const checklist = window.driverChecklist.checklist; ``` Continue reading the [Getting Started](https://driverjs.com/docs/basic-usage.md) guide to learn more about the package. --- # Basic Usage > Get started with Driver.js: import the library, configure your steps, and create your first multi-step product tour or element highlight in minutes. Once installed, you can import and start using the library. There are several different configuration options available to customize the library. You can find more details about the options in the [configuration section](https://driverjs.com/docs/configuration.md). Given below are the basic steps to get started. Here is a simple example of how to create a tour with multiple steps. ```js import { driver } from "driver.js"; import "driver.js/dist/driver.css"; const driverObj = driver({ showProgress: true, steps: [ { element: '.page-header', popover: { title: 'Title', description: 'Description' } }, { element: '.top-nav', popover: { title: 'Title', description: 'Description' } }, { element: '.sidebar', popover: { title: 'Title', description: 'Description' } }, { element: '.footer', popover: { title: 'Title', description: 'Description' } }, ] }); driverObj.drive(); ``` You can pass a single step configuration to the `highlight` method to highlight a single element. Given below is a simple example of how to highlight a single element. ```js import { driver } from "driver.js"; import "driver.js/dist/driver.css"; const driverObj = driver(); driverObj.highlight({ element: '#some-element', popover: { title: 'Title for the Popover', description: 'Description for it', }, }); ``` Beyond tours and highlights, Driver.js also ships hints: pulsing beacons that sit quietly on the page and open a popover when clicked, with no overlay and nothing blocked. They come from their own entry, so tour-only users never load them. ```js import { hints } from "driver.js/hints"; import "driver.js/dist/hints.css"; const productHints = hints({ hints: [ { element: '#export-btn', popover: { title: 'Export your data', description: 'Download this report as CSV or PDF.' } }, { element: '#summary', popover: { title: 'Auto-generated summary', description: 'Written for you from the numbers.' } }, ], }); productHints.show(); ``` Learn more about beacons, dismissals, and the optional overlay in the [Hints guide](https://driverjs.com/docs/hints.md). Examples above show the basic usage of the library. Find more details about the configuration options in the [configuration section](https://driverjs.com/docs/configuration.md) and the examples in the [examples section](https://driverjs.com/docs/examples). --- # Tour Basics > Build a multi-step product tour with Driver.js. Animate between steps or jump instantly, tune the animation speed, and show a step counter like 1 of 5. A tour is a list of steps, each highlighting an element with a popover. Pass the steps to `driver()` and call `drive()` to start. The examples below cover the options that shape how a tour looks and moves. ## Animated Tour Tours animate between steps by default. Click the button below the code sample to see the tour in action. ```js import { driver } from "driver.js"; import "driver.js/dist/driver.css"; const driverObj = driver({ showProgress: true, steps: [ { element: '#tour-example', popover: { title: 'Animated Tour Example', description: 'Here is the code example showing animated tour. Let\'s walk you through it.', side: "left", align: 'start' }}, { element: 'code .line:nth-child(1)', popover: { title: 'Import the Library', description: 'It works the same in vanilla JavaScript as well as frameworks.', side: "bottom", align: 'start' }}, { element: 'code .line:nth-child(2)', popover: { title: 'Importing CSS', description: 'Import the CSS which gives you the default styling for popover and overlay.', side: "bottom", align: 'start' }}, { element: 'code .line:nth-child(4) span:nth-child(7)', popover: { title: 'Create Driver', description: 'Simply call the driver function to create a driver.js instance', side: "left", align: 'start' }}, { element: 'code .line:nth-child(18)', popover: { title: 'Start Tour', description: 'Call the drive method to start the tour and your tour will be started.', side: "top", align: 'start' }}, { element: 'a[href="/docs/configuration"]', popover: { title: 'More Configuration', description: 'Look at this page for all the configuration options you can pass.', side: "right", align: 'start' }}, { popover: { title: 'Happy Coding', description: 'And that is all, go ahead and start adding tours to your applications.' } } ] }); driverObj.drive(); ``` ### Animation Duration Use the `duration` option to control how long the transition animation takes, in milliseconds. It controls both the speed at which the highlight moves between steps and the overlay/popover fade-in. It only applies when `animate` is `true` and defaults to `400`. Lower it for a snappier tour, or raise it for a more relaxed one. ```js import { driver } from "driver.js"; import "driver.js/dist/driver.css"; const driverObj = driver({ // Speed the whole transition up to 150ms (default is 400ms) duration: 150, showProgress: true, steps: [ { element: '#some-element', popover: { title: 'Import the Library', description: 'Notice how quickly the highlight snaps over to this line.' }}, { element: '#another-element', popover: { title: 'Create Driver', description: 'The popover fades in almost instantly at this speed.' }}, { element: '#yet-another-element', popover: { title: 'Fast Duration', description: 'A low duration makes the whole tour feel snappy.' }}, ] }); driverObj.drive(); ``` ```js import { driver } from "driver.js"; import "driver.js/dist/driver.css"; const driverObj = driver({ // Slow the whole transition down to 1200ms (default is 400ms) duration: 1200, showProgress: true, steps: [ { element: '#some-element', popover: { title: 'Import the Library', description: 'Notice how slowly the highlight glides over to this line.' }}, { element: '#another-element', popover: { title: 'Create Driver', description: 'The popover fade-in runs over the same duration as the slide.' }}, { element: '#yet-another-element', popover: { title: 'Slow Duration', description: 'This is the duration option that controls the whole transition.' }}, ] }); driverObj.drive(); ``` ## Static Tour Set the `animate` option to `false` to make the tour static. The tour will not animate between steps and will just show the popover. ```js import { driver } from "driver.js"; import "driver.js/dist/driver.css"; const driverObj = driver({ animate: false, showProgress: false, showButtons: ['next', 'previous', 'close'], steps: [ { element: '#tour-example', popover: { title: 'Static Tour Example', description: 'Here is the code example showing a static tour. Let\'s walk you through it.', side: "left", align: 'start' }}, { element: 'code .line:nth-child(1)', popover: { title: 'Import the Library', description: 'It works the same in vanilla JavaScript as well as frameworks.', side: "bottom", align: 'start' }}, { element: 'code .line:nth-child(2)', popover: { title: 'Importing CSS', description: 'Import the CSS which gives you the default styling for popover and overlay.', side: "bottom", align: 'start' }}, { element: 'code .line:nth-child(4) span:nth-child(7)', popover: { title: 'Create Driver', description: 'Simply call the driver function to create a driver.js instance', side: "left", align: 'start' }}, { element: 'code .line:nth-child(18)', popover: { title: 'Start Tour', description: 'Call the drive method to start the tour and your tour will be started.', side: "top", align: 'start' }}, { element: '#docs-sidebar a[href="/docs/configuration"]', popover: { title: 'More Configuration', description: 'Look at this page for all the configuration options you can pass.', side: "right", align: 'start' }}, { popover: { title: 'Happy Coding', description: 'And that is all, go ahead and start adding tours to your applications.' } } ] }); driverObj.drive(); ``` ## Tour Progress You can use `showProgress` option to show the progress of the tour. It is shown in the bottom left corner of the popover. There is also `progressText` option which can be used to customize the text shown for the progress. Please note that `showProgress` is `false` by default. Also the default text for `progressText` is `{{current}} of {{total}}`. You can use `{{current}}` and `{{total}}` in your `progressText` template to show the current and total steps. ```js import { driver } from "driver.js"; import "driver.js/dist/driver.css"; const driverObj = driver({ showProgress: true, showButtons: ['next', 'previous'], steps: [ { element: '#tour-example', popover: { title: 'Animated Tour Example', description: 'Here is the code example showing animated tour. Let\'s walk you through it.', side: "left", align: 'start' }}, { element: 'code .line:nth-child(1)', popover: { title: 'Import the Library', description: 'It works the same in vanilla JavaScript as well as frameworks.', side: "bottom", align: 'start' }}, { element: 'code .line:nth-child(2)', popover: { title: 'Importing CSS', description: 'Import the CSS which gives you the default styling for popover and overlay.', side: "bottom", align: 'start' }}, { element: 'code .line:nth-child(4) span:nth-child(7)', popover: { title: 'Create Driver', description: 'Simply call the driver function to create a driver.js instance', side: "left", align: 'start' }}, { element: 'code .line:nth-child(16)', popover: { title: 'Start Tour', description: 'Call the drive method to start the tour and your tour will be started.', side: "top", align: 'start' }}, ] }); driverObj.drive(); ``` --- # Configuration > Explore every Driver.js configuration option. Configure the driver globally, per step, or on the fly, with full TypeScript autocomplete and type safety. Driver.js is built to be highly configurable. You can configure the driver globally, or per step. You can also configure the driver on the fly, while it's running. > Configuration, steps, and state belong to the specific `driver()` instance you set them on. Each call to `driver()` is independent, so you can keep several instances on the same page and configuring one never affects another. Only one instance is shown at a time: driving or highlighting with one closes any other active instance. > Driver.js is written in TypeScript. Configuration options are mostly self-explanatory. Also, if you're using an IDE like WebStorm or VSCode, you'll get autocomplete and documentation for all the configuration options. ## Driver Configuration You can configure the driver globally by passing the configuration object to the `driver` call or by using the `setConfig` method. Given below are some of the available configuration options. ```typescript type Config = { // Array of steps to highlight. You should pass // this when you want to setup a product tour. steps?: DriveStep[]; // Whether to animate the product tour. (default: true) animate?: boolean; // Duration of the transition animation in milliseconds. Controls both the // speed of the stage moving between steps and the overlay/popover fade-in. // Only applies when `animate` is true. (default: 400) duration?: number; // Overlay color. (default: black) // This is useful when you have a dark background // and want to highlight elements with a light // background color. overlayColor?: string; // Whether to smooth scroll to the highlighted element. (default: false) smoothScroll?: boolean; // Whether to allow closing the popover by clicking on the backdrop. (default: true) allowClose?: boolean; // Whether to allow scrolling the page while the driver is active. // Set to false to lock body scrolling for the duration of the tour. (default: true) allowScroll?: boolean; // Opacity of the backdrop. (default: 0.5) overlayOpacity?: number; // What to do when the overlay backdrop is clicked. // Possible options are 'close', 'nextStep', 'none', or a custom function. (default: 'close') // Use 'none' to ignore overlay clicks while Escape and the close button still work. overlayClickBehavior?: | "close" | "nextStep" | "none" | (( element?: Element, step: DriveStep, options: { config: Config; state: State; driver: Driver; index: number | undefined } ) => void); // Distance between the highlighted element and the cutout. (default: 10) stagePadding?: number; // Radius of the cutout around the highlighted element. (default: 5) stageRadius?: number; // Whether to allow keyboard navigation. (default: true) // Arrow keys are ignored when their button is hidden or disabled, // and Escape is ignored when the close button is disabled. allowKeyboardControl?: boolean; // Whether to disable interaction with the highlighted element. (default: false) // Can be configured at the step level as well disableActiveInteraction?: boolean; // Advance the tour when the highlighted element is clicked, as if the next // button was pressed; onNextClick and onDoneClick still apply. The element's // own click behavior runs normally. Has no effect on steps where // disableActiveInteraction blocks clicks. Can be configured at the step // level. (default: false) advanceOnClick?: boolean; // Skip a step whose target element is specified but missing from the DOM, // moving on in the direction of travel instead of showing the centered // fallback popover. Steps without an element are intentional centered steps // and are never skipped. Can be configured at the step level. (default: false) skipMissingElement?: boolean; // Wait up to this many milliseconds for a step's element to appear before // treating it as missing (centered fallback popover, or a skip when // skipMissingElement is set). The current step stays highlighted, with its // popover hidden, while waiting. Useful when the next element is rendered on demand, e.g. inside // a modal that the previous step's click opens. Can be configured at the // step level. (default: 0, off) waitForElement?: number; // If you want to add custom class to the popover popoverClass?: string; // Distance between the popover and the highlighted element. (default: 10) popoverOffset?: number; // Array of buttons to show in the popover. Defaults to ["next", "previous", "close"] // for product tours and [] for single element highlighting. showButtons?: AllowedButtons[]; // Array of buttons to disable. This is useful when you want to show some of the // buttons, but disable some of them. disableButtons?: AllowedButtons[]; // Whether to show the progress text in popover. (default: false) showProgress?: boolean; // Template for the progress text. You can use the following placeholders in the template: // - {{current}}: The current step number // - {{total}}: Total number of steps progressText?: string; // Text to show in the buttons. `doneBtnText` // is used on the last step of a tour. nextBtnText?: string; prevBtnText?: string; doneBtnText?: string; // Screen reader label of the close button. Defaults to "Close". closeBtnLabel?: string; // Called after the popover is rendered. // PopoverDOM is an object with references to // the popover DOM elements such as buttons // title, descriptions, body, container etc. onPopoverRender?: ( popover: PopoverDOM, options: { config: Config; state: State; driver: Driver; index: number | undefined } ) => void; // Hooks to run before and after highlighting // each step. Each hook receives the following // parameters: // - element: The target DOM element of the step // - step: The step object configured for the step // - options.config: The current configuration options // - options.state: The current state of the driver // - options.driver: Current driver object // - options.index: Index of the currently active step onHighlightStarted?: ( element?: Element, step: DriveStep, options: { config: Config; state: State; driver: Driver; index: number | undefined } ) => void; onHighlighted?: ( element?: Element, step: DriveStep, options: { config: Config; state: State; driver: Driver; index: number | undefined } ) => void; onDeselected?: ( element?: Element, step: DriveStep, options: { config: Config; state: State; driver: Driver; index: number | undefined; reason?: DestroyReason } ) => void; // Hooks to run before and after the driver // is destroyed. Each hook receives // the following parameters: // - element: Currently active element // - step: The step object configured for the currently active // - options.config: The current configuration options // - options.state: The current state of the driver // - options.driver: Current driver object // - options.index: Index of the currently active step // - options.reason: Why the tour is ending // "done" moved past the last step // "close" the close button // "escape" the escape key // "overlay" a click on the overlay // "replaced" another driver instance started // "api" destroy() or navigation from your own code // A destroy() you call from a hook (e.g. confirming in // onDestroyStarted) keeps the reason of the action that // started it. onDeselected also gets it when it runs on destroy. onDestroyStarted?: ( element?: Element, step: DriveStep, options: { config: Config; state: State; driver: Driver; index: number | undefined; reason: DestroyReason } ) => void; onDestroyed?: ( element?: Element, step: DriveStep, options: { config: Config; state: State; driver: Driver; index: number | undefined; reason: DestroyReason } ) => void; // Hooks to run on button clicks. Each hook receives // the following parameters: // - element: The current DOM element of the step // - step: The step object configured for the step // - options.config: The current configuration options // - options.state: The current state of the driver // - options.driver: Current driver object // - options.index: Index of the currently active step onNextClick?: ( element?: Element, step: DriveStep, options: { config: Config; state: State; driver: Driver; index: number | undefined } ) => void; onPrevClick?: ( element?: Element, step: DriveStep, options: { config: Config; state: State; driver: Driver; index: number | undefined } ) => void; onCloseClick?: ( element?: Element, step: DriveStep, options: { config: Config; state: State; driver: Driver; index: number | undefined } ) => void; // Runs when the done button (the next button on the // last step of a tour) is clicked. When provided, it // runs instead of `onNextClick` on the last step. onDoneClick?: ( element?: Element, step: DriveStep, options: { config: Config; state: State; driver: Driver; index: number | undefined } ) => void; }; ``` > **Note**: By overriding `onNextClick`, and `onPrevClick` hooks you control the navigation of the driver. This means that user won't be able to navigate using the buttons and you will have to either call `driverObj.moveNext()` or `driverObj.movePrevious()` to navigate to the next/previous step. > > You can use this to implement custom logic for navigating between steps. This is also useful when you are dealing with dynamic content and want to highlight the next/previous element based on some logic. > > `onNextClick` and `onPrevClick` hooks can be configured at the step level as well. When configured at the driver level, you control the navigation for all the steps. When configured at the step level, you control the navigation for that particular step only. > > `onDoneClick` lets you detect when the user finishes the tour by clicking the done button on the last step. When provided, it runs instead of `onNextClick` on the last step, and Driver.js won't destroy the tour automatically; you control teardown by calling `driverObj.destroy()` yourself. Like the other click hooks, it can be configured at the driver or step level. > `options.index`: Every hook receives the zero-based index of the currently active step in `options.index` (equivalent to `driverObj.getActiveIndex()`), or `undefined` when driving a single highlight rather than a `steps` array. Note that `index` always reflects the _active_ step, so inside `onDeselected` (which fires for the step being left during a transition) it points at the step being moved to, not the one being deselected. > `options.reason`: `onDestroyStarted`, `onDestroyed` and the `onDeselected` that runs when the tour ends receive why it ended: `"done"` (moved past the last step, including when the remaining steps are skipped as missing), `"close"`, `"escape"`, `"overlay"`, `"replaced"` (another driver instance started) or `"api"` (`destroy()`, or a `moveTo()` / `movePrevious()` that left the steps, from your own code). It is not set on the other hooks, or on the `onDeselected` that runs between steps. A `destroy()` you call from a hook (`onDestroyStarted`, `onCloseClick`, `onNextClick` / `onDoneClick` on the last step, an `overlayClickBehavior` function) keeps the reason of the user action that started it. This hook only asks when the user leaves early: > > ```js > onDestroyStarted: (el, step, { reason }) => { > if (reason === "done" || confirm("Leave?")) { > driverObj.destroy(); > } > }; > ``` > > The type is exported as `DestroyReason`. ## Popover Configuration The popover is the main UI element of Driver.js. It's the element that highlights the target element, and shows the step content. You can configure the popover globally, or per step. Given below are some of the available configuration options. ```typescript type Popover = { // Title and descriptions shown in the popover. // You can use HTML in these. Also, you can // omit one of these to show only the other. title?: string; description?: string; // Which side of the target element the popover is // placed on. Auto-flips to the opposite side when it // doesn't fit the viewport. (default: "bottom") side?: "top" | "right" | "bottom" | "left"; // 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. // (default: "start") align?: "start" | "center" | "end"; // Array of buttons to show in the popover. // When highlighting a single element, there // are no buttons by default. When showing // a tour, the default buttons are "next", // "previous" and "close". showButtons?: ("next" | "previous" | "close")[]; // An array of buttons to disable. This is // useful when you want to show some of the // buttons, but disable some of them. disableButtons?: ("next" | "previous" | "close")[]; // Text to show in the buttons. `doneBtnText` // is used on the last step of a tour. nextBtnText?: string; prevBtnText?: string; doneBtnText?: string; // Screen reader label of the close button. Defaults to "Close". closeBtnLabel?: string; // Whether to show the progress text in popover. showProgress?: boolean; // Template for the progress text. You can use // the following placeholders in the template: // - {{current}}: The current step number // - {{total}}: Total number of steps // Defaults to following if `showProgress` is true: // - "{{current}} of {{total}}" progressText?: string; // Custom class to add to the popover element. // This can be used to style the popover. popoverClass?: string; // Hook to run after the popover is rendered. // You can modify the popover element here. // Parameter is an object with references to // the popover DOM elements such as buttons // title, descriptions, body, etc. onPopoverRender?: ( popover: PopoverDOM, options: { config: Config; state: State; driver: Driver; index: number | undefined } ) => void; // Callbacks for button clicks. You can use // these to add custom behavior to the buttons. // Each callback receives the following parameters: // - element: The current DOM element of the step // - step: The step object configured for the step // - options.config: The current configuration options // - options.state: The current state of the driver // - options.driver: Current driver object // - options.index: Index of the currently active step onNextClick?: ( element?: Element, step: DriveStep, options: { config: Config; state: State; driver: Driver; index: number | undefined } ) => void; onPrevClick?: ( element?: Element, step: DriveStep, options: { config: Config; state: State; driver: Driver; index: number | undefined } ) => void; onCloseClick?: ( element?: Element, step: DriveStep, options: { config: Config; state: State; driver: Driver; index: number | undefined } ) => void; // Runs instead of `onNextClick` when the done button // (the next button on the last step) is clicked. onDoneClick?: ( element?: Element, step: DriveStep, options: { config: Config; state: State; driver: Driver; index: number | undefined } ) => void; }; ``` ## Drive Step Configuration Drive step is the configuration object passed to the `highlight` method or the `steps` array of the `drive` method. You can configure the popover and the target element for each step. Given below are some of the available configuration options. ```typescript type DriveStep = { // The target element to highlight. // This can be a DOM element, // a function that returns a DOM Element, or a CSS selector. // If this is a selector, the first matching // element will be highlighted. element?: Element | string | (() => Element); // The popover configuration for this step. // Look at the Popover Configuration section popover?: Popover; // Whether to disable interaction with the highlighted element. (default: false) disableActiveInteraction?: boolean; // Advance the tour when this step's highlighted element is clicked. // Overrides the driver-level option for this step. (default: false) advanceOnClick?: boolean; // Skip this step when its target element is specified but missing from the // DOM. Overrides the driver-level option for this step. (default: false) skipMissingElement?: boolean; // Wait up to this many milliseconds for this step's element to appear before // treating it as missing. Overrides the driver-level option. (default: 0, off) waitForElement?: number; // Arbitrary data that you can pass to the step in order to support complex custom logic in hooks. data?: Record; // Callback when the current step is deselected, // about to be highlighted or highlighted. // Each callback receives the following parameters: // - element: The current DOM element of the step // - step: The step object configured for the step // - options.config: The current configuration options // - options.state: The current state of the driver // - options.driver: Current driver object // - options.index: Index of the currently active step // - options.reason: Why the tour ended, only on the // onDeselected that runs on destroy (see Driver Configuration) onDeselected?: ( element?: Element, step: DriveStep, options: { config: Config; state: State; driver: Driver; index: number | undefined; reason?: DestroyReason } ) => void; onHighlightStarted?: ( element?: Element, step: DriveStep, options: { config: Config; state: State; driver: Driver; index: number | undefined } ) => void; onHighlighted?: ( element?: Element, step: DriveStep, options: { config: Config; state: State; driver: Driver; index: number | undefined } ) => void; }; ``` ## Hints Configuration Hints are configured through the `hints()` factory from the `driver.js/hints` entry. See the [Hints guide](https://driverjs.com/docs/hints.md) for an introduction and examples; the full options are given below. ```typescript type HintsConfig = { // Array of hints to show. See the Hint // Configuration section below for the format. hints?: DriverHint[]; // Beacon defaults applied to every hint; // a hint's own beacon values win. beacon?: HintBeacon; // Text of the dismiss button. (default: "Got it") buttonText?: string; // Custom class and offset for the hint popovers, // same meaning as in tours. popoverClass?: string; popoverOffset?: number; // Dim the page while a hint is open, with the hint's // element cut out like a tour step. The popover then // anchors to the element instead of the beacon, the // open hint's beacon steps aside, and the other // beacons wait under the dim. Clicking the dimmed // page closes the hint. (default: false) overlay?: boolean; // Color and opacity of the dim. (defaults: "#000" and 0.7) overlayColor?: string; overlayOpacity?: number; // Called when a hint popover is opened, and when a // hint is dismissed (via the button or `dismiss()`). // Each hook receives the following parameters: // - element: The hint's target DOM element // - hint: The hint object // - options.config: The hints configuration // - options.hints: The hints instance onOpen?: (element: Element, hint: DriverHint, options: { config: HintsConfig; hints: Hints }) => void; onDismiss?: (element: Element, hint: DriverHint, options: { config: HintsConfig; hints: Hints }) => void; // Runs instead of dismissing when the built-in button is clicked, // like a tour's onNextClick takes over the default advance. Call // options.hints.dismiss(hint.id) yourself to also remove the hint. // Can be configured per hint as well. onButtonClick?: (element: Element, hint: DriverHint, options: { config: HintsConfig; hints: Hints }) => void; }; ``` ## Hint Configuration Each entry of the `hints` array describes one hint: the element it points at, the beacon that sits on it, and the popover that opens on click. ```typescript type DriverHint = { // The element the hint points at. This can be a DOM // element, a function that returns a DOM element, or // a CSS selector. A hint whose element is missing is // skipped, and picked up on the next show() if the // element has appeared since. element: Element | string | (() => Element); // Stable identity used by open/dismiss/restore and // passed to the hooks; useful for persisting // dismissals. Defaults to the hint's index. id?: string; // Where the beacon sits on the element's box, and // how it looks. beacon?: { // Edge of the element the beacon sits on. (default: "top") side?: "top" | "right" | "bottom" | "left"; // Position along that edge. (default: "end") align?: "start" | "center" | "end"; // Nudge the beacon in pixels from its anchor point. // Positive moves right/down, negative left/up. // (default: 0) offsetX?: number; offsetY?: number; // Whether the beacon pulses. The pulse also pauses // automatically for users who prefer reduced // motion. (default: true) animate?: boolean; // Custom class for the beacon, e.g. to scope the // --driver-hint-size and --driver-hint-color // CSS variables to it. className?: string; }; // The popover that opens when the beacon is clicked. // Rendered exactly like a tour popover. popover?: { title?: string; description?: string; side?: "top" | "right" | "bottom" | "left"; align?: "start" | "center" | "end"; popoverClass?: string; // The dismiss button. Hide it for hints you only // dismiss from code. (default: true) showButton?: boolean; // Overrides the instance-level buttonText. buttonText?: string; // Overrides the instance-level onButtonClick. onButtonClick?: (element: Element, hint: DriverHint, options: { config: HintsConfig; hints: Hints }) => void; // Hook to run after the popover is rendered, // same as in tours but with the hint and the // hints instance in the options. onPopoverRender?: (popover: PopoverDOM, options: { hint: DriverHint; hints: Hints }) => void; }; // Hint-level hooks, taking precedence over the // instance-level ones. onOpen?: (element: Element, hint: DriverHint, options: { config: HintsConfig; hints: Hints }) => void; onDismiss?: (element: Element, hint: DriverHint, options: { config: HintsConfig; hints: Hints }) => void; // Arbitrary data to carry along, available wherever // the hint object is. data?: Record; }; ``` ## Checklist Configuration Checklists are configured through the `checklist()` factory from the `driver.js/checklist` entry. See the [Checklists guide](https://driverjs.com/docs/checklists.md) for an introduction and examples; the full options are given below. ```typescript type ChecklistConfig = { // Identifies the checklist in events. id: string; // Array of items. See the Checklist Item // Configuration section below for the format. items: ChecklistItem[]; // Text in the panel header, below the title, and on the // launcher button. (defaults: "Get started", none, the title) title?: string; description?: string; launcherText?: string; // Shown instead of the title, description and launcher // text once every item is done, and swapped back if an // item is unticked. An empty completeDescription hides // the description. completeTitle?: string; completeDescription?: string; completeLauncherText?: string; // Progress text next to the bar; {{done}}, {{total}}, // {{remaining}} and {{percent}} are replaced. // (default: "{{done}} of {{total}}") progressText?: string; // The count on the launcher, with the same placeholders. // An empty string hides it. (default: "{{done}}/{{total}}") launcherProgressText?: string; // false hides the floating launcher, for pages that open the panel // from their own button with open() or toggle(). (default: true) launcher?: boolean; // Corner of the screen. In the top corners the panel // opens below the launcher. (default: "bottom-right") position?: "bottom-right" | "bottom-left" | "top-right" | "top-left"; // Distance in pixels from the screen edges. // (default: 20, or 12 on screens narrower than 480px) offset?: { x?: number; y?: number }; // Extra class on the widget. className?: string; // Turns the check circle into a button, so users can tick // items off, or untick them, themselves. (default: false) allowManualComplete?: boolean; // Ids of the items that are already done, for progress // you saved earlier. completed?: string[]; // Called on every change with the ids of the done items // (including ids no longer in `items`) and the count. onProgress?: (progress: { completed: string[]; done: number; total: number }) => void; // Called when an item is ticked, and once when every // item is done. onItemComplete?: (item: ChecklistItem, options: { checklist: Checklist }) => void; onComplete?: (options: { checklist: Checklist }) => void; // Called with every checklist event: shown, hidden, opened, // closed, item_started, item_completed, item_uncompleted, // tour_ended, completed and reset. tour_ended carries the // tour's destroy reason. onEvent?: (event: { kind: "checklist"; id: string; type: string; itemId?: string; reason?: DestroyReason; timestamp: number; }) => void; }; ``` ## Checklist Item Configuration Each entry of the `items` array is one task. Clicking it starts its tour, follows its link or runs its action. ```typescript type ChecklistItem = { // Stable identity, used for progress. Keep it the // same when you reword or reorder items. id: string; // HTML is allowed, like in popovers. title: string; description?: string; // A tour to run: steps, a driver configuration or a // driver() instance. The item is ticked when the user // finishes the tour, not when they close it early. tour?: DriveStep[] | Config | Driver; // A link to follow. The item is ticked on click. href?: string; // Your own code to run. The item is ticked afterwards. action?: (item: ChecklistItem, options: { checklist: Checklist }) => void; // Ticks the item once it returns true. Checked on show(), // refresh() and after the item's tour or action, and it // replaces the default ticking above. completeWhen?: () => boolean; // Overrides the checklist's allowManualComplete for this item. allowManualComplete?: boolean; // Your own data, available on the item in hooks. data?: Record; }; ``` ## State You can access the current state of the driver by calling the `getState` method. It's also passed to the hooks and callbacks. ```typescript type State = { // Whether the driver is currently active or not isInitialized?: boolean; // Index of the currently active step if using // as a product tour and have configured the // steps array. activeIndex?: number; // DOM element of the currently active step activeElement?: Element; // Step object of the currently active step activeStep?: DriveStep; // DOM element that was previously active previousElement?: Element; // Step object of the previously active step previousStep?: DriveStep; // DOM elements for the popover i.e. including // container, title, description, buttons etc. popover?: PopoverDOM; }; ``` --- # Interactive Tours > Build Driver.js tours that react to your app: advance when users click the highlighted element, wait for elements that render on demand, run async logic between steps, and continue across pages. Sometimes the best way forward in a tour is the product itself: click the button you are pointing at, not a "Next" in the popover. Two options make this work without any custom hooks: - `advanceOnClick` advances the tour when the highlighted element is clicked, exactly as if the next button was pressed. The element's own click behavior still runs. - `waitForElement` makes a step wait up to the given number of milliseconds for its element to appear, staying on the current step with its popover hidden in the meantime. Both can be set for the whole tour or per step. ## Advance on Click The tour below has `advanceOnClick` enabled, so you can click the highlighted lines to walk through it. ```js import { driver } from "driver.js"; import "driver.js/dist/driver.css"; const driverObj = driver({ advanceOnClick: true, showProgress: true, steps: [ { element: '#pick-plan', popover: { title: 'Pick a Plan', description: 'Click the highlighted card to continue.' } }, { element: '#billing-toggle', popover: { title: 'Billing', description: 'Clicking advances the tour; the toggle still flips.' } }, { element: '#checkout-btn', popover: { title: 'Checkout', description: 'Clicking the last element ends the tour.' } }, ], }); driverObj.drive(); ``` > **Tip**: For truly click-driven steps, hide the next button with `showButtons: ['close']` on the step's popover so the highlighted element is the only way forward. Note that `advanceOnClick` has no effect on steps where `disableActiveInteraction` blocks clicks on the element. ## Wait for Elements Click-driven tours usually lead somewhere new: the click opens a modal or a dropdown whose elements don't exist yet. Give the next step a `waitForElement` timeout and the tour waits for the element instead of falling back. In the demo below, pressing next renders the target element about a second later; watch the tour hold the current step, then follow. ```js import { driver } from "driver.js"; import "driver.js/dist/driver.css"; const driverObj = driver({ steps: [ { element: '#open-modal-btn', advanceOnClick: true, popover: { title: 'Open the Modal', description: 'Clicking this button opens the modal and moves the tour on.', showButtons: ['close'], }, }, { // Rendered on demand: the tour waits up to 5 seconds for it, // staying on the previous step in the meantime. element: '#modal-confirm', waitForElement: 5000, popover: { title: 'Confirm', description: 'This step appeared once the modal rendered.', }, }, ], }); driverObj.drive(); ``` If the element never appears, the wait times out into the usual missing-element behavior: the centered fallback popover, or a skip when `skipMissingElement` is set. ## Async Steps When a step needs to load data or render something before the tour can move on, and `waitForElement` isn't enough, take over navigation yourself. Override `onNextClick`, do the work, then call `moveNext()`. ```js import { driver } from "driver.js"; import "driver.js/dist/driver.css"; const driverObj = driver({ showProgress: true, steps: [ { popover: { title: 'First Step', description: 'This is the first step. Next element will be loaded dynamically.' // By passing onNextClick, you can override the default behavior of the next button. // This will prevent the driver from moving to the next step automatically. // You can then manually call driverObj.moveNext() to move to the next step. onNextClick: () => { // .. load element dynamically // .. and then call driverObj.moveNext(); }, }, }, { element: '.dynamic-el', popover: { title: 'Async Element', description: 'This element is loaded dynamically.' }, // onDeselected is called when the element is deselected. // Here we are simply removing the element from the DOM. onDeselected: () => { // .. remove element document.querySelector(".dynamic-el")?.remove(); } }, { popover: { title: 'Last Step', description: 'This is the last step.' } } ] }); driverObj.drive(); ``` > **Note**: By overriding `onNextClick`, and `onPrevClick` hooks you control the navigation of the driver. This means that user won't be able to navigate using the buttons and you will have to either call `driverObj.moveNext()` or `driverObj.movePrevious()` to navigate to the next/previous step. > > You can use this to implement custom logic for navigating between steps. This is also useful when you are dealing with dynamic content and want to highlight the next/previous element based on some logic. > > `onNextClick` and `onPrevClick` hooks can be configured at driver level as well as step level. When configured at the driver level, you control the navigation for all the steps. When configured at the step level, you control the navigation for that particular step only. ## Multi-Page Tours A driver instance lives within a single page: a full page load destroys it, and even in single-page apps the next route's elements don't exist while the current route is showing. The reliable pattern for tours that span navigation is to treat the tour as resumable: persist the step to resume at, let the app navigate normally, and start a fresh tour from the saved step on the next page. ### Saving Progress Before Navigating Define the tour in a function so every page can create it. On the step whose click navigates away, use `advanceOnClick` together with an `onNextClick` override: instead of moving to a step that lives on another page, save where to resume and tear the tour down. The link's own click keeps working, so navigation happens naturally. ```js import { driver } from "driver.js"; import "driver.js/dist/driver.css"; const TOUR_KEY = "app-tour-step"; export function createTour() { let isNavigating = false; const driverObj = driver({ steps: [ { element: "#reports-nav", popover: { title: "Reports", description: "Your reports live here." }, }, { element: "#settings-link", // Clicking the highlighted link continues the tour and navigates. advanceOnClick: true, popover: { title: "Open Settings", description: "Click this link to continue on the settings page.", showButtons: ["close"], onNextClick: () => { // The next step lives on another page: save where to resume // and let the click's navigation happen naturally. isNavigating = true; localStorage.setItem(TOUR_KEY, "2"); driverObj.destroy(); }, }, }, // These steps target elements on the settings page. waitForElement // absorbs the rendering delay after a client-side route change. { element: "#profile-form", waitForElement: 5000, popover: { title: "Your Profile", description: "Update your details here." }, }, { element: "#save-settings", popover: { title: "Save", description: "Don't forget to save your changes." }, }, ], onDestroyed: () => { // Finished or exited: clear the saved progress so the tour does not // come back on the next visit. Skipped for the hand-off destroy above. if (!isNavigating) { localStorage.removeItem(TOUR_KEY); } }, }); return driverObj; } ``` ### Resuming on the Next Page On every page load (or in your router's mount hook), check for saved progress and resume from it: ```js const savedStep = localStorage.getItem(TOUR_KEY); if (savedStep !== null) { createTour().drive(Number(savedStep)); } ``` `drive()` accepts the step index to start from, so the tour picks up exactly where it left off. ### Notes - The pattern works the same for full page loads and client-side routing. On a full load the browser tears everything down anyway; the explicit `destroy()` in the hand-off matters for single-page apps, where the old page's tour would otherwise stay on screen. - The saved value here is a hardcoded index for clarity. In a larger tour, compute it with `driverObj.getActiveIndex() + 1`, or store a step id and look up its index when resuming, so reordering steps doesn't break saved progress. - If the destination page renders its content asynchronously, `waitForElement` on the first resumed step keeps the tour from falling back to the centered popover while the page hydrates. See [Wait for Elements](#wait-for-elements) above. --- # API Reference > Complete Driver.js API reference: every method available on the driver, hints and checklist instances, with examples. Here is the list of methods provided by `driver` when you initialize it. > **Note:** We have omitted the configuration options for brevity. Please look at the configuration section for the options. Links are provided in the description below. ```javascript import { driver } from "driver.js"; import "driver.js/dist/driver.css"; // Look at the configuration section for the options // https://driverjs.com/docs/configuration#driver-configuration const driverObj = driver({ /* ... */ }); // -------------------------------------------------- // driverObj is an object with the following methods // -------------------------------------------------- // Start the tour using `steps` given in the configuration driverObj.drive(); // Starts at step 0 driverObj.drive(4); // Starts at step 4 driverObj.moveNext(); // Move to the next step driverObj.movePrevious(); // Move to the previous step driverObj.moveTo(4); // Move to the step 4 driverObj.hasNextStep(); // Is there a next step driverObj.hasPreviousStep(); // Is there a previous step driverObj.isFirstStep(); // Is the current step the first step driverObj.isLastStep(); // Is the current step the last step driverObj.getActiveIndex(); // Gets the active step index driverObj.getActiveStep(); // Gets the active step configuration driverObj.getPreviousStep(); // Gets the previous step configuration driverObj.getNextStep(); // Gets the next step configuration driverObj.getActiveElement(); // Gets the active HTML element driverObj.getPreviousElement(); // Gets the previous HTML element // Is the tour or highlight currently active driverObj.isActive(); // Recalculate and redraw the highlight driverObj.refresh(); // Look at the configuration section for configuration options // https://driverjs.com/docs/configuration#driver-configuration driverObj.getConfig(); driverObj.setConfig({ /* ... */ }); driverObj.setSteps([ /* ... */ ]); // Set the steps // Look at the state section of configuration for format of the state // https://driverjs.com/docs/configuration#state driverObj.getState(); // Look at the DriveStep section of configuration for format of the step // https://driverjs.com/docs/configuration/#drive-step-configuration driverObj.highlight({ /* ... */ }); // Highlight an element // Destroy the tour. The destroy hooks receive `options.reason` "api", // or the reason of the user action when called from inside a hook. // https://driverjs.com/docs/configuration#driver-configuration driverObj.destroy(); ``` > Each `driver()` call returns its own independent instance. You can create as many as you need on the same page, and their configuration, steps, and state never overlap. Only one instance can be on screen at a time, so starting a tour or highlight on one instance closes whichever instance is currently active. ## Hints Hints ship as their own entry and factory. See the [Hints guide](https://driverjs.com/docs/hints.md) for an introduction, and the [Hints Configuration](https://driverjs.com/docs/configuration.md#hints-configuration) section for the options. ```javascript import { hints } from "driver.js/hints"; import "driver.js/dist/hints.css"; // Look at the configuration section for the options // https://driverjs.com/docs/configuration#hints-configuration const productHints = hints({ /* ... */ }); // ----------------------------------------------------- // productHints is an object with the following methods // ----------------------------------------------------- // Mount the beacons. Calling it again picks up hints // whose elements have appeared since. productHints.show(); // Remove the beacons and listeners; show() brings them back productHints.hide(); // Hints are addressed by their `id`, or by their // index in the array when no id is set. productHints.open("export"); // Open a hint's popover productHints.close(); // Close the open popover, keeping its beacon productHints.dismiss("export"); // Remove the hint's beacon and fire 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 // Reposition the beacons, the open popover and the // overlay after layout changes productHints.refresh(); ``` > Like `driver()`, each `hints()` call is independent: its hints, dismissals, and overlay never affect another instance or a running tour. ## Checklists Checklists ship as their own entry and factory. See the [Checklists guide](https://driverjs.com/docs/checklists.md) for an introduction, and the [Checklist Configuration](https://driverjs.com/docs/configuration.md#checklist-configuration) section for the options. ```javascript import { checklist } from "driver.js/checklist"; import "driver.js/dist/driver.css"; import "driver.js/dist/checklist.css"; // Look at the configuration section for the options // https://driverjs.com/docs/configuration#checklist-configuration const onboarding = checklist({ /* ... */ }); // --------------------------------------------------- // onboarding is an object with the following methods // --------------------------------------------------- // Add the launcher to the page; hide() removes the widget onboarding.show(); onboarding.hide(); onboarding.open(); // Open the panel onboarding.close(); // Close the panel, keeping the launcher onboarding.toggle(); // Open or close the panel // Items are addressed by their `id`. onboarding.start("tour"); // Run the item's tour, link or action onboarding.complete("github"); // Tick an item onboarding.uncomplete("github"); // Untick an item onboarding.reset(); // Untick every item // Check every item's completeWhen again onboarding.refresh(); onboarding.getProgress(); // { completed: ["tour"], done: 1, total: 4 } onboarding.getItems(); // The configured items onboarding.isVisible(); // Whether the widget is on the page onboarding.isOpen(); // Whether the panel is open ``` > Tours started from a checklist run like any other tour, so starting one closes whichever tour is currently active. Items that are done can still be clicked to run their tour again. --- # Ending a Tour > Control how a Driver.js tour ends. Ask users to confirm before they leave, stop them from exiting early, and tell a finished tour apart from one that was closed. A tour ends when the user finishes the last step, or leaves early with the close button, the Escape key or a click on the overlay. The examples below show how to step in before the tour closes, and how to find out which way it ended. ## Confirm on Exit You can use the `onDestroyStarted` hook to add a confirmation dialog or some other logic when the user tries to exit the tour. In the example below, we ask for confirmation only when the user leaves before finishing the tour. ```js import { driver } from "driver.js"; import "driver.js/dist/driver.css"; const driverObj = driver({ showProgress: true, steps: [ { element: '#confirm-destroy-example', popover: { title: 'Animated Tour Example', description: 'Here is the code example showing animated tour. Let\'s walk you through it.', side: "left", align: 'start' }}, { element: 'code .line:nth-child(1)', popover: { title: 'Import the Library', description: 'It works the same in vanilla JavaScript as well as frameworks.', side: "bottom", align: 'start' }}, { element: 'code .line:nth-child(2)', popover: { title: 'Importing CSS', description: 'Import the CSS which gives you the default styling for popover and overlay.', side: "bottom", align: 'start' }}, { popover: { title: 'Happy Coding', description: 'And that is all, go ahead and start adding tours to your applications.' } } ], // onDestroyStarted is called when the user tries to exit the tour onDestroyStarted: (element, step, { reason }) => { if (reason === "done" || confirm("Are you sure?")) { driverObj.destroy(); } }, }); driverObj.drive(); ``` > **Note:** By overriding the `onDestroyStarted` hook, you are responsible for calling `driverObj.destroy()` to exit the tour. ## Prevent Exit You can also prevent the user from exiting the tour using `allowClose` option. This option is useful when you want to force the user to complete the tour before they can exit. In the example below, you won't be able to exit the tour until you reach the last step. ```js import { driver } from "driver.js"; import "driver.js/dist/driver.css"; const driverObj = driver({ showProgress: true, allowClose: false, steps: [ { element: '#prevent-exit', popover: { title: 'Animated Tour Example', description: 'Here is the code example showing animated tour. Let\'s walk you through it.', side: "left", align: 'start' }}, { element: 'code .line:nth-child(1)', popover: { title: 'Import the Library', description: 'It works the same in vanilla JavaScript as well as frameworks.', side: "bottom", align: 'start' }}, { element: 'code .line:nth-child(2)', popover: { title: 'Importing CSS', description: 'Import the CSS which gives you the default styling for popover and overlay.', side: "bottom", align: 'start' }}, { popover: { title: 'Happy Coding', description: 'And that is all, go ahead and start adding tours to your applications.' } } ], }); driverObj.drive(); ``` ## Finished or Closed `onDestroyStarted` and `onDestroyed` receive `reason` in their options, telling you why the tour ended: - `"done"`: the user moved past the last step - `"close"`, `"escape"` or `"overlay"`: the user left early with the close button, the Escape key or a click on the overlay - `"replaced"`: another tour or highlight started - `"api"`: your own code called `destroy()` Use it to mark onboarding as complete only when the user actually finished it, or to log the step where users drop off. Finish the tour below, or leave it early, to see the reason. ```js import { driver } from "driver.js"; import "driver.js/dist/driver.css"; const driverObj = driver({ steps: [ // ... ], onDestroyed: (element, step, { reason, index }) => { if (reason === "done") { markOnboardingComplete(); } else { trackDropOff({ reason, step: index }); } }, }); driverObj.drive(); ``` > **Note:** A `destroy()` you call from a hook keeps the reason of the user action that started it. In the [Confirm on Exit](#confirm-on-exit) example above, `onDestroyed` gets `"close"`, not `"api"`. --- # Simple Highlight > Highlight any element on the page with Driver.js. Spotlight a single element and show a popover to deliver contextual help, beyond just product tours. Product tours is not the only usecase for Driver.js. You can use it to highlight any element on the page and show a popover with a description. This is useful for providing contextual help to the user e.g. help the user fill a form or explain a feature. Example below shows how to highlight an element and simply show a popover. Here is the code for above example: ```js const driverObj = driver({ popoverClass: "driverjs-theme", stagePadding: 4, }); driverObj.highlight({ element: "#highlight-me", popover: { side: "bottom", title: "This is a title", description: "This is a description", } }) ``` You can also use it to show a simple modal without highlighting any element. Here is the code for above example: ```js const driverObj = driver(); driverObj.highlight({ popover: { description: "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; } ```