Scroll Effects

Hero Parallax Grid

A product/portfolio wall with alternating row parallax — each row sweeps in a different direction as you scroll, with a 3D reveal.

This is a WebberUI Pro component

The install command below needs a licence to run; without one it returns 401.

How to install Pro components →See the plans →

A scroll-driven hero parallax wall: items are split into several rows, adjacent rows shift horizontally in opposite directions, and rotateX plus a fade-in complete within the first 20% of the reveal, producing a sense of moving through depth.

Bring this into your project

Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.

npx webberui@latest add "https://webberui.com/r/hero-parallax-grid.en.json?t=<install token>"

New project or installation trouble? Start here →

Hero Parallax Grid is a React scroll parallax showcase built with Tailwind CSS and Motion (useScroll, useTransform). The images are split evenly into rows; as the page scrolls, neighboring rows slide horizontally in opposite directions, and the whole group straightens from a tilted 3D angle and fades in during the first part of the scroll.

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

3
320
200
150
Active edited usageDefault props
<HeroParallaxGrid/>

The complete example includes the preview layout and data. Install the component first, then paste this file into your project.

When to use

  • The hero section of a design studio or freelancer homepage, showing many work thumbnails at once.
  • A SaaS or app site showing its template library, themes or screenshots.
  • New collections or a product wall on an e-commerce brand page.
  • An event or exhibition recap page that sweeps through a large set of photos.
  • Not a good fit: for a single row of images or logos looping continuously, use Velocity Marquee.

Installation

npx webberui@latest add "https://webberui.com/r/hero-parallax-grid.en.json?t=<install token>"

Or, once registries are configured in components.json, install it as @webberui/hero-parallax-grid.

Usage

import { HeroParallaxGrid } from "@/components/ui/hero-parallax-grid";

const items = [
  { src: "/work/nova.jpg", alt: "Nova", title: "Nova" },
  { src: "/work/ember.jpg", alt: "Ember", title: "Ember" },
  // 9–15 images recommended
];

<HeroParallaxGrid
  items={items}
  title="Selected work"
  description="Alternating row parallax."
/>;

The height of the outer track container (the h-* in className) determines the scrollable distance; the taller it is, the slower and more complete the parallax sweep. If the scrolling happens inside a nested overflow section, pass that container's ref to container.

Examples

Passing image URLs only

items also accepts a plain string array; images without alt are marked aria-hidden as decorative. cardWidth and cardHeight set each card's size.

import { HeroParallaxGrid } from "@/components/ui/hero-parallax-grid";

const shots = Array.from({ length: 12 }, (_, i) => `/templates/template-${i + 1}.jpg`);

<HeroParallaxGrid
  items={shots}
  title="Ready-to-use page templates"
  description="Sign-in, pricing, blog and dashboard layouts you can copy and edit."
  cardWidth={240}
  cardHeight={160}
/>

Rows, speed and image styles

rows sets how many rows the items are split into, and travel is how far each row slides over the full scroll (px); set the outer height with className, where a taller track means a longer scroll. imageClassName applies to every image, and since each card has group, you can use group-hover: variants.

<HeroParallaxGrid
  items={works}
  rows={2}
  travel={480}
  className="h-[2000px]"
  imageClassName="grayscale transition duration-300 group-hover:grayscale-0"
  aria-label="Recent work"
/>

Inside a scrollable container

When scrolling happens inside a nested overflow block, such as a preview pane, pass that container's ref to container so scroll progress is measured against it.

"use client";

import * as React from "react";
import { HeroParallaxGrid } from "@/components/ui/hero-parallax-grid";

export function PreviewPane({ items }: { items: string[] }) {
  const scrollerRef = React.useRef<HTMLDivElement>(null);

  return (
    <div ref={scrollerRef} className="h-[600px] overflow-y-auto rounded-xl border">
      <HeroParallaxGrid items={items} container={scrollerRef} className="h-[1400px]" />
    </div>
  );
}

Props

PropTypeDefaultDescription
items(string | { src; alt?; title? })[]—Product/portfolio items; 9–15 recommended
containerRefObject<HTMLElement>undefinedNested scroll container; the window is the scroll source by default
rowsnumber3How many rows to split into; adjacent rows shift in opposite directions
travelnumber320Horizontal displacement per row (px); larger sweeps faster
titlestringundefinedTitle at the top (not rendered when omitted)
descriptionstringundefinedDescription below the title
cardWidthnumber200Card width (px)
cardHeightnumber150Card height (px)
aria-labelstring"Parallax product wall"Accessible name for the gallery
classNamestringundefinedThe track container; its height determines the scrollable distance
imageClassNamestringundefinedApplied to every card image

Accessibility

  • When the user has "reduce motion" enabled at the system level, a static grid is rendered instead, with no scroll parallax
  • The whole set carries role="group" and aria-label; images without an alt are marked aria-hidden

FAQ

Does it work with the Next.js App Router?

Yes. The component file already starts with "use client", so import it from a Server Component page and pass items. The first render always outputs the parallax version and only switches based on the reduced-motion setting after mount, so server and client initial output match.

Are images optimized with next/image?

No. Cards use a plain <img> (object-cover, not draggable). If you need optimization, pass URLs already sized by your CDN or image service, or edit ProductCard in the source.

The rows sweep too fast or too slow. How do I tune it?

Two things control it: travel (how far each row slides, default 320 px) and the outer height (default h-[1600px], overridden with className). With the same travel, a taller container scrolls more slowly.

How many images should I use?

The source recommends 9 to 15. Items are split evenly across rows, with each row holding the total divided by the row count, rounded up.

What happens with reduced motion?

With "reduce motion" enabled, the component renders a static responsive grid (2 to 4 columns) that keeps the title and description, with no scroll parallax or hover lift. The only dependencies are motion, clsx and tailwind-merge.