Command Palette

Search for a command to run...

Docs
Scroll Tilted Grid

Scroll Tilted Grid

React carousel component — an editorial two-column image grid where pairs of stills rise from below tipped forward, settle into focus, then tilt away over.

Installation

Usage

Full-page (default)

The component ships with sectionPadding="20vh", so the grid breathes on a landing page without any wrapper work — page scroll drives the choreography.

import { ScrollTiltedGrid } from "@/components/ruixen/scroll-tilted-grid";
 
export default function Page() {
  return (
    <main className="relative min-h-screen overflow-x-hidden">
      <section className="flex min-h-screen flex-col items-center justify-center px-6 text-center">
        <h1 className="text-3xl font-medium tracking-tight md:text-5xl">
          A field of stills
        </h1>
      </section>
 
      <ScrollTiltedGrid loop />
    </main>
  );
}

Inside a bounded container

Pass a ref to a fixed-height self-scrolling ancestor via container, and shrink sectionPadding so the grid fits the smaller frame. useScroll and the loop's IntersectionObserver measure against the ref instead of the viewport.

import { useRef } from "react";
import { ScrollTiltedGrid } from "@/components/ruixen/scroll-tilted-grid";
 
export default function Preview() {
  const ref = useRef<HTMLDivElement>(null);
  return (
    <div ref={ref} className="h-[480px] overflow-y-auto rounded-xl">
      <ScrollTiltedGrid
        container={ref}
        loop
        maxCycles={4}
        sectionPadding="1rem"
        maxWidth="md"
        gap={6}
      />
    </div>
  );
}

Custom images

<ScrollTiltedGrid
  images={[
    "/gallery/01.jpg",
    "/gallery/02.jpg",
    "/gallery/03.jpg",
    "/gallery/04.jpg",
  ]}
/>

Props

PropTypeDefaultDescription
imagesreadonly string[]DEFAULT_GRID_IMAGESImage URLs to render.
loopbooleanfalseCycle the source list and append more pairs as the user nears the bottom — a perceptually infinite scroll.
initialCyclesnumber3Initial number of cycles to render when loop is on.
aspectRatiostring"3/4"CSS aspect-ratio value applied to each tile (e.g. "3/4", "2/3").
maxWidth"sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "none""lg"Tailwind max-w-* token controlling the column width.
gap4 | 6 | 8 | 10 | 12 | 1410Tailwind gap-* token between tiles.
perspectivenumber900CSS perspective (px) applied to each tile.
maxTiltnumber70Maximum rotateX magnitude (deg) at the entry and exit poses. Symmetric — entry tilts forward +maxTilt, exit tilts back -maxTilt.
maxBlurnumber8Maximum blur (px) at the entry and exit poses.
roundedstring"0.375rem"CSS border-radius for the tile clipping mask. Accepts any CSS length value.
containerRefObject<HTMLElement | null>-Optional ref to a scrollable ancestor. When set, scroll progress and the loop sentinel are measured against it instead of the viewport.
maxCyclesnumberInfinityHard cap on total cycles when loop is on. Set a finite value to bound DOM growth in long sessions.
sectionPaddingstring"20vh"Vertical breathing room (margin + padding) around the grid. Use "0" or a small rem value when embedding in a bounded container.
classNamestring-Additional className applied to the outer <section>.

Features

  • Scroll-driven choreography: Each tile uses useScroll with a start end → end start offset so its tilt, blur, and translate are tied directly to its position in the viewport.
  • Symmetric entry / exit pose: Tiles tilt forward +maxTilt on entry, settle flat at the midpoint, then tilt back -maxTilt as they exit over the top edge.
  • Alternating sides: Even-indexed tiles drift in from the left, odd-indexed from the right, producing a paired editorial cadence.
  • Infinite loop (opt-in): With loop, an IntersectionObserver sentinel appends two more cycles whenever the user gets within ~1500px of the bottom.
  • Respects prefers-reduced-motion: Tiles render as static stills when the user has reduced motion enabled — no transforms, no filters, no blur.
  • Composed transforms: x, y, z, rotate, rotateX, and skewX are applied together via Framer Motion's hardware-accelerated transform pipeline; an inner scaleY adds a subtle vertical squash so the tilt reads as physical rather than flat.