Light the way.
Leave the door open.
The spotlight you can click through. A product tour that cuts a hole in the overlay, so your users work the real thing underneath.
npm install @annetaan/lekoScroll, and follow the light
01 · Why another tour
Every other tour puts glass over the button.
A highlight is usually a transparent layer. It looks like the button is right there, but the click lands on the layer. So a tour can only point and say “click here”, and the user watches.
A layer the click hits the glass
A hole the click hits the button
02 · Signals
A step ends when something really happened.
Steps never advance on clicks. Your app says what finished, and the step waiting for it moves on. A step that waits has no next button, because that would be a way past the work it asks for.
The story
const checkout = { id: 'checkout', steps: [ { id: 'cart', target: '#cart', message: 'Your cart.' }, { id: 'pay', target: { elements: '#pay', interactive: true }, message: 'Pay for real. The tour waits.', awaits: 'order-saved', }, ],} satisfies LekoStoryYour app, where the order really saved
await api.saveOrder(cart)leko.reached('order-saved')03 · Typed signals
Typos are caught before anyone runs the tour.
@annetaan/leko-codegen collects every signal your app reports with reached() and turns them into a type. A step can only wait for one of them, so a misspelt signal is a compile error, not a tour that waits forever.
{ id: 'pay', awaits: 'ordr-saved' }TS2322Type "ordr-saved" is not assignable to type "order-saved" | "profile-filled".
04 · Yours to shape
Styled with CSS. Shipped with nothing else.
Every colour, radius and shadow is a CSS custom property, so the tour looks like your app. And Leko has no runtime dependencies at all.
Your stylesheet
:root { --leko-scrim-color: #000a; --leko-halo-outline: 2px solid gold; --leko-message-radius: 12px;}0
runtime dependencies
One more thing
The light you followed down this page is Leko, too.
Leko Scroll is a spotlight for a long page. No story, no steps, nothing to click: the light moves as the reader scrolls, and the page underneath stays fully usable. It ships in the same package.
This page, more or less
import { createScroll } from '@annetaan/leko/scroll'
createScroll({ targets: [ { target: '#why-copy' }, { target: '#why', message: { title: 'A layer, or a hole' } }, { target: '#signals-copy' }, { target: '#signals', message: { title: 'No click listener' } }, ],})Now try it for real.
The playground runs a Leko tour over a real page. Type, click, and watch it wait for you.