apps/docs/src/content/guides/hints.mdx
import { HintsSample } from "../../components/HintsSample.tsx";
Tours walk users through your product step by step. Hints do the opposite: they sit quietly on the page as pulsing beacons, and the user decides which ones to open, in any order, whenever they want. There is no overlay and nothing is blocked. The page stays fully interactive.
Hints ship as their own entry, so tour-only users never load them. Import the
module and its stylesheet. The stylesheet is self-contained, so 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:
<HintsSample buttonText="Show Hints" config={{ overlay: true, overlayOpacity: 0.5 }} hints={[ { element: "#hint-demo-export", id: "export", popover: { title: "Export your data", description: "Download this report as CSV or PDF.", }, }, { element: "#hint-demo-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", }, }, ]} client:load />
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.
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", 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", 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.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