Pushes in on one region of live UI the way a screen recording does, and dims the rest behind a cutout. Built on Motion and the Product demo set's target model (eased-demo-core).
When to use
- The payoff moment of a product demo: the result a click produced.
- Drawing the eye to one card, row or chart in a dense screen for a few seconds.
- Recording a launch clip from the page itself, with the push-in done in the browser.
When not to use
- A tour the person steps through, with a tooltip and Back and Next: use Spotlight Tour.
- Letting someone inspect detail on demand: use a Dialog or Image Compare.
- Highlighting something the person is about to operate: zooming moves it under their pointer.
Installation
npx shadcn add https://eased.dev/r/zoom-spotlight.jsonThe command adds Zoom Spotlight, everything it builds on and Eased’s tokens to your project.
Usage
import { Card, CardContent } from "@/components/eased/card/card";import { ZoomSpotlight } from "@/components/eased/zoom-spotlight/zoom-spotlight";export function Example({ live }: { live: boolean }) { return ( <ZoomSpotlight active={live} target="result" className="h-80"> <div className="grid h-full grid-cols-2 gap-4 p-4"> <Card> <CardContent>Usage</CardContent> </Card> <Card data-demo="result"> <CardContent>Deployed to fathom.app</CardContent> </Card> </div> </ZoomSpotlight> );}Give the frame a size; it clips what the zoom pushes out of view.
Examples
Driven by a timeline
const { step } = useDemoTimeline(STEPS, { endStep: "zoom", observe: stage });<ZoomSpotlight active={step === "zoom"} target="result" className="min-h-0 flex-1">…</ZoomSpotlight>Light without zooming
scale={1} keeps the view still and only dims around the target.
<ZoomSpotlight active target={rowRef} scale={1}>…</ZoomSpotlight>API reference
ZoomSpotlight
| Prop | Type | Default | Description |
|---|---|---|---|
active | boolean | Zoom onto the target and dim the rest; off springs back. | |
target | DemoTarget | null | A ref, or a data-demo name inside the children. | |
scale | number | 1.6 | The most it zooms. It zooms less when the target would not fit. |
padding | number | 8 | Space between the target and the edge of the lit area, in px. |
dim | boolean | true | Dim everything outside the target. |
className | string | "" | Classes for the frame; give it a size. |
children | ReactNode | The UI to zoom. |
A Demo Cursor or Click Ripple placed among the children zooms with the content, like a cursor in a recording.
Accessibility
- The zoom is a transform and the dim is an
aria-hiddenlayer: nothing in the content changes for assistive tech. - Dimmed content is still there; if it must not be operated while lit, make the region
inert.
Motion
Scale and pan ride the smooth spring (no overshoot), so the target settles centred and the view never shows past its own edges. The dim is the scrim colour behind a rounded cutout concentric with the target's corner; it fades in over 300ms on the enter curve and out in 150ms on the exit curve as the view springs back. A resize mid-zoom retargets the same spring. Under reduced motion it never zooms; the dim fades in and out in 100ms.
Notes for AI
Use Zoom Spotlight for the moment a demo should land on: wrap the stage in it, mark the result with data-demo, and set active from a useDemoTimeline step. Keep the target smaller than the frame (a card, not a full-width row), or there is nothing to zoom into.
Do not compute transforms or cutouts yourself, and do not nest a second scrim. It already measures through its own scale, follows resizes and keeps the cutout for the fade out.
Related
- Spotlight Tour: a guided tour with steps and a tooltip (Pro).
- Demo Cursor: points at what the zoom lands on.
- Product demo set: the pieces on one timeline.