Image Trail Card
An interactive card that leaves a trail of fading image afterimages along the cursor's path, with in-place fade and drift variants.
This is a WebberUI Pro component
Free during the launch campaign: sign up or sign in, then hit “Copy install command” in the preview above and it installs straight away — no payment, no credit card. The command below returns 401 while you are signed out.
npx shadcn@latest add "https://webberui.com/r/image-trail-card.json?t=<install token>"Playground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<ImageTrailCard />
Installation
npx shadcn@latest add "https://webberui.com/r/image-trail-card.json?t=<install token>"Or, once registries are configured in components.json, install it as @webberui/image-trail-card.
Usage
import { ImageTrailCard } from "@/components/ui/image-trail-card";
<ImageTrailCard
images={[
"/gallery/01.jpg",
"/gallery/02.jpg",
"/gallery/03.jpg",
{ src: "/gallery/04.jpg", alt: "Work four" },
]}
className="aspect-[3/2] w-full max-w-md"
>
<p className="text-sm text-neutral-500">Move the cursor</p>
</ImageTrailCard>images accepts either string URLs or objects carrying an alt. The list is cycled through as the cursor moves and repeats automatically when there are not enough entries, so even a handful of images forms a continuous trail.
Variants
fade(default): the afterimage fades out in place with a slight scale.drift: the afterimage also drifts upward as it fades, giving the trail more flow.
<ImageTrailCard images={images} variant="drift" />Props
| Prop | Type | Default | Description |
|---|---|---|---|
images | Array<string | { src: string; alt?: string }> | — | List of afterimage sources, cycled through in order as the cursor moves |
variant | "fade" | "drift" | "fade" | Afterimage style: fade in place, or drift upward while fading |
imageSize | number | 120 | Edge length of a single afterimage (px) |
threshold | number | 60 | Minimum cursor displacement needed to spawn the next afterimage (px); smaller values make a denser trail |
lifetime | number | 0.9 | How long a single afterimage lives from appearance to disappearance (seconds) |
rotate | number | 8 | Maximum random rotation applied to each afterimage (deg); set 0 for no rotation |
children | React.ReactNode | — | Base content placed beneath the trail layer (title, hint text, and so on) |
className | string | — | Appended to the outermost container's className (set the size and aspect ratio here) |
Remaining <div> attributes (such as aria-label and style) are forwarded to the outermost container; onPointerMove and onPointerLeave are called after the internal handling runs.
How it works
- Afterimages come from a fixed node pool driven by Motion's imperative
animate; while the cursor moves, DOM coordinates and transforms are written directly, triggering no React re-render, so even rapid movement stays smooth. - The node pool is padded based on the length of
imagesto at least 12 and capped at 30, so afterimages are not recycled too early and the DOM does not grow without bound. - Trail density is controlled by "spawn the next one only once accumulated displacement exceeds
threshold" rather than spawning on everypointermove. childrensits beneath the trail layer as the card's base content and is set topointer-events-noneso it does not interfere with cursor tracking.
Accessibility
- When the user has "reduce motion" enabled at the system level, afterimage spawning is disabled, the card stays a static container, and the
childrencontent displays as usual. - The trail layer is marked
aria-hiddenand the images default toalt=""— decorative only, so it does not interfere with assistive technology. - On unmount, every in-flight afterimage animation is stopped and any pending animation controls are cleaned up.