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:
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:
Revenue is up 12% quarter over quarter. Click the beacons to explore what changed. The page stays fully interactive while they are shown.
Here is the code for the example above:
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();The two are deliberately different:
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.
Driver.js keeps dismissals in memory for the session and stays out of the storage business. onDismiss with stable ids is the hook, and storage is yours:
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();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. Set animate: false for a static dot; the pulse also pauses automatically for users who prefer reduced motion.
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:
const hint = {
element: "#dashboard",
beacon: { side: "top", align: "end", offsetX: -12, offsetY: 8 },
};Size and colour come from CSS variables:
.driver-hint {
--driver-hint-size: 32px;
--driver-hint-color: #e11d48;
}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:
const productHints = hints({
overlay: true,
overlayColor: "#000",
overlayOpacity: 0.5,
hints: [...],
});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:
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.
Configuration passed to hints():
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:
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:
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