25
Componentregistry:component

Image Trail

Moving the pointer leaves a trail of images behind it. Each card rises under the cursor, tilts with the direction of travel, hangs for a moment and drifts away. Recycles a fixed pool of cards, keeps quiet zones clear for headlines and buttons, loads images lazily, mouse and pen only, with reduced-motion support.

Installation

$ npx shadcn@latest add @creative-ui/image-trail

On an older shadcn CLI, use the URL: npx shadcn@latest add https://ui.krutikraut.com/r/image-trail.json

Preview

Move your pointer

this label is a quiet zone

Spacing80px
Duration1500ms
Tilt16°
Max cards14
Card shape

Usage

Example.tsx
import { ImageTrail } from "@/components/image-trail"

const images = [
  { src: "/art/bust.webp", alt: "Marble bust" },
  { src: "/art/muse.webp", alt: "A muse reading" },
  { src: "/art/owl.webp", alt: "An owl on a keyboard" },
]

export function Hero() {
  return (
    <section className="relative h-[80svh] overflow-hidden">
      <ImageTrail images={images} />

      {/* Cards never spawn over the headline and buttons. */}
      <div data-trail-quiet className="relative z-10 mx-auto w-fit pt-24">
        <h1>Timeless craft.</h1>
      </div>
    </section>
  )
}

Props

PropTypeDefaultDescription
images*{ src: string; alt?: string }[]—Images to drop along the pointer path, used in order and looped.
spacingnumber80Pointer travel in px between two cards. Smaller is denser.
durationnumber1500Life of one card in ms: rise, hang, drift away.
maxCardsnumber14Most cards on screen at once. Cards are recycled, oldest first, so the DOM never grows.
tiltnumber16Largest tilt in degrees, following the direction of travel. 0 keeps cards upright.
quietSelectorstring"[data-trail-quiet]"Selector for areas inside the parent that never spawn cards, plus half a card of margin.
cardClassNamestring—Classes for every card: size, aspect ratio, radius, shadow. Defaults to a 4:5 card, 110 to 168px wide.

Also accepts every div prop, which is spread onto the root element.

Notes

  • Place it inside a positioned element (relative, absolute, fixed). It covers that element, listens for pointer moves on it and stays out of the way of clicks.
  • Give your own content a higher z-index to keep it above the trail, or a lower one to let cards pass over it.
  • Mouse and pen only, so touch scrolling never sprays cards. Images load once the page is idle, and only on devices with a mouse or pen.
  • Cards move with transform and opacity through the Web Animations API, so the trail costs no layout and no React renders while the pointer moves.
  • With reduced motion the cards fade in place without rising, tilting or scaling.
  • The trail is decorative and hidden from assistive tech. Put anything people need to read in the page itself.
  • In Next.js, pass resized URLs from getImageProps (next/image) so the trail does not download full-size images.

Dependencies

shadcn/utils