Spotlight (guided focus)
@ponchia/ui/css/spotlight.css is an opt-in guided-focus overlay — a
dimming layer with a cutout over a target element, an optional ring, and a
callout note. It's the visual language of a product tour or onboarding step.
@import '@ponchia/ui';
@import '@ponchia/ui/css/spotlight.css';
Bronto is not a tour engine. It owns the look and (via initSpotlight)
positions the cutout over a target. Step order, advancing, persistence, and
whether the overlay is shown are the host's job — Bronto deliberately stays out
of that state machine.
Markup
<div
class="ui-spotlight ui-spotlight--ring"
data-bronto-spotlight
data-target="save-button"
role="region"
aria-label="Tour"
>
<div class="ui-spotlight__hole"></div>
<div class="ui-tour-note" style="position: absolute; …place near the hole…">
<span class="ui-tour-note__step">Step 2 of 4</span>
<h2 class="ui-tour-note__title">Save your work</h2>
<p class="ui-tour-note__body">Changes autosave, but you can force a save here.</p>
<div class="ui-tour-note__actions">
<button class="ui-button ui-button--ghost" type="button">Skip</button>
<button class="ui-button" type="button">Next</button>
</div>
</div>
</div>
import { initSpotlight } from '@ponchia/ui/behaviors';
const stop = initSpotlight(); // positions the cutout; re-places on resize/scroll
// Advance the tour by pointing at the next target — the cutout follows:
document.querySelector('[data-bronto-spotlight]').dataset.target = 'next-thing';
// Hide it when the tour ends (host-owned):
document.querySelector('[data-bronto-spotlight]').hidden = true;
Pieces
| Class | Role |
|---|---|
ui-spotlight |
The fixed overlay. Non-blocking (pointer-events: none) — a visual highlight, not a modal trap. |
ui-spotlight__hole |
The cutout. Dims the page via one box-shadow; positioned by --spot-x/y/w/h. |
ui-spotlight--ring |
Adds an accent ring around the cutout. |
ui-tour-note |
The callout card (re-enables pointer events for its controls). |
ui-tour-note__step / __title / __body / __actions |
Callout parts. |
ui.spotlight({ ring }) builds the overlay class string.
How positioning works
initSpotlight reads each [data-bronto-spotlight]'s data-target id, measures
that element with getBoundingClientRect, and sets --spot-x/y/w/h (viewport
coordinates) on the overlay. Because the overlay is position: fixed, those map
directly. It re-places on resize, scroll, and whenever data-target changes —
so stepping a tour is just updating data-target. --spot-pad insets the
cutout from the target; --spot-radius rounds it.
Accessibility notes
- The overlay is non-blocking by design (the dim is a box-shadow, not an
interaction trap). If a step must block interaction, that's a host concern —
add your own inert/
aria-hiddenhandling around it. - Put the tour's real content in
.ui-tour-note(a focusable region with a heading), not in the visual dim, so screen-reader users get the same guidance. - Keep the callout's label stable and move focus to it when a step opens (host).