WebberUI

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.

How to install Pro components →See the plans →

Loading preview…
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.

120
60
0.9
8
<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

PropTypeDefaultDescription
imagesArray<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
imageSizenumber120Edge length of a single afterimage (px)
thresholdnumber60Minimum cursor displacement needed to spawn the next afterimage (px); smaller values make a denser trail
lifetimenumber0.9How long a single afterimage lives from appearance to disappearance (seconds)
rotatenumber8Maximum random rotation applied to each afterimage (deg); set 0 for no rotation
childrenReact.ReactNodeBase content placed beneath the trail layer (title, hint text, and so on)
classNamestringAppended 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 images to 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 every pointermove.
  • children sits beneath the trail layer as the card's base content and is set to pointer-events-none so 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 children content displays as usual.
  • The trail layer is marked aria-hidden and the images default to alt="" — 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.

On this page