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.
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>"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.
<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
| Prop | Type | Default | Description |
|---|---|---|---|
items | (string | { src; alt?; title? })[] | — | Product/portfolio items; 9–15 recommended |
container | RefObject<HTMLElement> | undefined | Nested scroll container; the window is the scroll source by default |
rows | number | 3 | How many rows to split into; adjacent rows shift in opposite directions |
travel | number | 320 | Horizontal displacement per row (px); larger sweeps faster |
title | string | undefined | Title at the top (not rendered when omitted) |
description | string | undefined | Description below the title |
cardWidth | number | 200 | Card width (px) |
cardHeight | number | 150 | Card height (px) |
aria-label | string | "Parallax product wall" | Accessible name for the gallery |
className | string | undefined | The track container; its height determines the scrollable distance |
imageClassName | string | undefined | Applied 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"andaria-label; images without analtare markedaria-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.