Open sourceMITAbout 15 kB
Show people around your product.
Docent spotlights one element, explains it in a sentence or two, and gets out of the way. Tours are plain JSON, so they are easy to write, review and change.
pnpm add @docentjs/domInvoices
April · 24 open
A still of one step.
How it works
A tour is a small JSON document.
You describe the steps, point each one at an element, and start it. Docent draws the spotlight, places the popover, follows the element as the page scrolls and resizes, and remembers where each user got to.
Describe the steps
Each step has a title, a sentence or two, and the element it points at.
const tour = defineTour({ id: 'welcome', steps: [ { id: 'hi', title: 'Welcome' }, { id: 'new', target: { name: 'new' }, title: 'Create a project' }, ],})Name the elements
A data-docent name survives redesigns and refactors, unlike a CSS class.
<button data-docent="new"> New project</button>Start it
Start one tour yourself, or let the manager decide who sees which tour, and when.
createTour(tour).start()
// or, for many tours with rules:createDocent({ tours: [welcome, billing] })Live
Change a value. The tour follows.
Below, a tour is being edited on its own and the real library redraws the step after each change: the arrow, the spotlight, the theme, even which element it points at. Watch it as the file, or as the devtools panel you would use in your own app.
An animation of a tour being edited, either as its JSON file or in the Docent devtools panel, with a preview that updates after each change.
Make it yours
Quiet by default. Expressive when you want it.
The default is a small caret, a soft spotlight and a dimmed page. Pick anything below to see it on the button. Every choice is one field in the tour's JSON, for the whole tour or a single step. Or start from a ready-made theme, one command away.
Details
Made carefully where it counts.
- Small
- About 15 kB compressed for the engine and the web renderer together. Drawn arrows load only when a tour uses one.
- Accessible
- Dialog semantics, focus kept inside the popover and returned after, full keyboard control, reduced motion, and 44 px touch targets.
- Isolated
- The popover renders in a shadow root. Your CSS cannot break it, and its CSS cannot leak into your page.
- Mobile-ready
- On narrow screens the popover docks near the bottom as a card and scrolls the target clear of it.
- Data, not code
- Tours are JSON. Store them in your repo, load them from an API, or edit them in a builder.
- Rules built in
- Triggers, conditions on user traits, and per-user frequency decide who sees which tour.
- Any framework
- React, Vue and Svelte bindings, each about 1 kB. Or none at all.
- Devtools
- See why a tour is or is not showing, edit it live, audit targets and contrast.
- Good with AI
- A published JSON Schema, plain-text docs and a checker, so an assistant can write tours and prove they are valid.
Frameworks
Fits the app you already have.
One package per framework, each a thin layer over the same engine. Use the built-in popover, or render your own component and keep the spotlight, positioning and keyboard handling.
import { DocentDevtools } from '@docentjs/devtools/react'import { useDocent } from '@docentjs/react'import { billing, welcome } from './tours'
export function App({ user }) { const docent = useDocent({ tours: [welcome, billing] }) useEffect(() => docent.identify(user.id, { plan: user.plan }), [user])
return <DocentDevtools docent={docent} />}<script setup lang="ts">import { useDocent } from '@docentjs/vue'import { billing, welcome } from './tours'
const docent = useDocent({ tours: [welcome, billing] })docent.identify(user.id, { plan: user.plan })</script><script lang="ts"> import { useDocent } from '@docentjs/svelte' import { billing, welcome } from './tours'
const docent = useDocent({ tours: [welcome, billing] }) docent.identify(user.id, { plan: user.plan })</script>import { createDocent } from '@docentjs/dom'import { billing, welcome } from './tours'
const docent = createDocent({ tours: [welcome, billing] })docent.identify(user.id, { plan: user.plan })Start here