Spotlight
Isolate one thing and say something about it: the page dims and blurs, a hole is cut over the target, and a callout points at it.
Live demo
Spotlight is the coachmark: the way to say this one, here about something the user is looking at. Everything but the target dims (and optionally blurs), a hole is cut over it, a ring traces it, and a callout floats beside it carrying whatever you want to say — rich markup, actions, the lot.
A Tour is a Spotlight with more than one step. The isolation is a single clipped veil rather than four boxes packed around the target, and that choice is what makes the component honest: the hole can take real corner radii (it traces the target's own by default) or be a circle, and the element underneath stays live — the veil takes the pointer, the hole does not, so the thing you are pointing at is still the thing the user can press. A spotlight that greys out the button it is telling you to click is a screenshot with a circle drawn on it. The hole follows the target through scrolls, resizes, and layouts settling in, because a hole that drifts off its target is the single most obvious way this can look broken. The callout is a real <xtyle-popover>, so the placement, the edge-flipping, the arrow and the focus handling come from there rather than from a second implementation; what the element adds is the veil, the ring, the pointer, and the geometry that keeps them glued to whatever they are pointing at.
When to use
How this component composes with the rest of the set.
Props
15 props, straight from the manifest.
| Prop | Type | Default | Bindings | Description |
|---|---|---|---|---|
Appearance
States
open
The veil is up, the hole is cut, and the callout is anchored to the target.
pulse
The ring breathes, so the eye finds the target rather than merely accepting it. slow and fast set the cadence; both stop under reduced motion.
Anatomy
The named parts that make up the component, with their selectors.
spotlight
The fixed layer over the page. It paints nothing itself and takes no pointer events; its children do.
veil
The dimmed (and optionally blurred) layer, clipped by an evenodd path() that is the viewport minus the target. The only part that takes the pointer — a click on it dismisses.
ring
The outline traced around the hole, so the target reads as chosen rather than merely un-dimmed.
callout
The composed <xtyle-popover> holding what you have to say, anchored to the target and flipping at the viewport's edge.
panel
The callout's content column: the heading, the body, and the action row.
heading
The callout's title, wired to the panel via aria-labelledby.
close
The built-in dismiss button ("Got it"). Suppress it with noCloseButton when the actions slot carries its own.
pointer
The arrow at the target — the thing that says there. Bounces toward it by default, and stills under prefers-reduced-motion.
Tokens & coverage
What the component consumes, checked live against what the algorithm produces.
Live coverage check against the xtyle-default register
(derive(xtyleDefault, { anchors }) →
coverComponent(manifest, register)). Every token this component
consumes must be a key the algorithm produces.
--accent
--accent-fg
--border-thick
--border-thin
--duration-base
--duration-fast
--ease-emphasized
--ease-standard
--fg-0
--fg-1
--font-sans
--layer-veil
--leading-normal
--leading-tight
--radius-sm
--ring
--scrim
--space-2
--space-3
--state-hover
--state-press
--surface-overlay-border
--text-body
--text-lg
--weight-semibold
Slots
What the callout says. Rich markup: prose, a list, an image, whatever the step needs.
Buttons in the callout's action row, before the built-in dismiss. Where a Tour puts Back / Next / Skip.
Accessibility
Code
A coachmark on the save button
The canonical shape: isolate the control, say what it does, and let the user press the thing you're pointing at.
<xtyle-button id="save" variant="solid" tone="accent">Save</xtyle-button>
<xtyle-spotlight
id="coach"
target="#save"
heading="Save as you go"
shape="auto"
pulse
arrow="bounce"
scroll-into-view
>
Your work is kept locally until you press this. Nothing leaves the machine before then.
</xtyle-spotlight>
<script>
document.getElementById("coach").show();
</script>
<script lang="ts">
import { Button, Spotlight } from "@xtyle/svelte";
let coaching = $state(true);
</script>
<Button id="save" variant="solid" tone="accent">Save</Button>
<Spotlight bind:open={coaching} target="#save" heading="Save as you go" pulse>
Your work is kept locally until you press this.
{#snippet actions()}
<Button variant="ghost" tone="neutral" onclick={() => (coaching = false)}>Skip</Button>
{/snippet}
</Spotlight>
---
import { Button, Spotlight } from "@xtyle/astro";
---
<Button id="save" variant="solid" tone="accent">Save</Button>
<Spotlight id="coach" target="#save" heading="Save as you go" placement="right" pulse>
Your work is kept locally until you press this.
</Spotlight>
<script>
const coach = document.getElementById("coach") as HTMLElement & { show(): void };
if (!localStorage.getItem("coached")) coach.show();
coach.addEventListener("dismiss", () => localStorage.setItem("coached", "1"));
</script>