Editorial Interleave Grid
An editorial interleave grid — a product grid that automatically inserts a full-width image, pull quote, or video section every N cards, giving it a magazine-like rhythm.
npx shadcn@latest add https://webberui.com/r/editorial-interleave-grid.jsonPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<EditorialInterleaveGrid />
Installation
npx shadcn@latest add https://webberui.com/r/editorial-interleave-grid.jsonOr, once registries are configured in components.json, install it as @webberui/editorial-interleave-grid.
Usage
Product cards go in as children and editorial sections go in through the editorials array. The grid inserts one editorial section every cadence product cards, cycling through editorials.
import {
EditorialInterleaveGrid,
ProductCard,
EditorialBlock,
} from "@/components/ui/editorial-interleave-grid";
<EditorialInterleaveGrid
columns={3}
cadence={3}
editorials={[
<EditorialBlock
key="hero"
variant="image"
kicker="Autumn 2026"
title="A return to natural tones"
cta="Explore the collection"
href="/collections/autumn"
media={<img src="/hero.jpg" alt="" />}
/>,
<EditorialBlock
key="quote"
variant="quote"
quote="Good design leaves the noise outside the door."
author="— Brand director"
/>,
<EditorialBlock
key="film"
variant="video"
kicker="Behind the seams"
title="A day in the workshop"
media={<img src="/poster.jpg" alt="" />}
/>,
]}
>
{items.map((item) => (
<ProductCard
key={item.id}
title={item.name}
price={item.price}
tag={item.tag}
href={item.url}
image={<img src={item.image} alt="" />}
/>
))}
</EditorialInterleaveGrid>Props
EditorialInterleaveGrid
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | — | Product card children (put ProductCards here) |
editorials | React.ReactNode[] | [] | Editorial sections interleaved on the cadence, cycled through in order |
cadence | number | 3 | How many product cards go between each inserted editorial section |
columns | number | 3 | Number of product columns (1–6) |
gap | number | 16 | Grid gap (px) |
stagger | number | 0.06 | Interval between product cards on reveal (seconds) |
delay | number | 0 | Delay before the reveal starts (seconds) |
once | boolean | true | Plays only the first time the element enters the viewport |
className | string | — | Appended to the grid container's className |
ProductCard
| Prop | Type | Default | Description |
|---|---|---|---|
title | React.ReactNode | — | Product name |
price | React.ReactNode | — | Price text |
tag | React.ReactNode | — | Corner tag (such as "New" or "Limited") |
image | React.ReactNode | — | Product image node; a neutral placeholder shows when it is not supplied |
href | string | — | Link URL; when supplied the whole card is an <a> |
onSelect | () => void | — | Click callback; without an href the whole card is a <button> |
children | React.ReactNode | — | Override the content of the image area |
className | string | — | Appended to the card's className |
EditorialBlock
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "image" | "quote" | "video" | "image" | Style: full-width image / brand pull quote / video poster |
kicker | React.ReactNode | — | Kicker (a small label) |
title | React.ReactNode | — | Main title (image / video styles) |
quote | React.ReactNode | — | Quote content (quote style) |
author | React.ReactNode | — | Quote attribution / brand signature |
media | React.ReactNode | — | Background media node; a gradient shows when it is not supplied |
cta | React.ReactNode | — | Call-to-action text |
href | string | — | Call-to-action link; when supplied the whole block is clickable |
children | React.ReactNode | — | Override all of the content |
className | string | — | Appended to the block's className |
Animation details
- Editorial sections: they reveal by expanding a
clip-pathoutward from a horizontal seam in the middle (inset(46%)→inset(0%)); once the frame is open the text rises slightly into view, creating the rhythm of a magazine gatefold opening. - Product cards: staggered fade in (
opacityplus offset), with the interval controlled bystagger; the image scales up slightly on hover. - Video style: the play button carries a soft pulsing glow, hinting that it is playable.
- Interleave logic: it walks the product cards and inserts the next editorial section every time
cadenceis reached; nothing is inserted at the very end, so the sequence never closes on an orphaned section.
Accessibility
- When the user has "reduce motion" enabled at the system level, a static grid renders with no clip-path expansion, no fade in, and no pulsing
- Product names are rendered as
<h3>, and the quote style uses the semantic<blockquote>and<cite>tags - A product card with an
hrefrenders as an<a>and one with onlyonSelectrenders as a<button>, both keyboard focusable with afocus-visibleoutline - Decorative icons in the editorial sections (quote mark, play, glow) are all marked
aria-hidden
Zoomable Density Grid
A density zoom grid — switch the number of columns per row the way iOS Photos does, with every cell reflowing smoothly through a FLIP animation.
Sieve Filter Grid
A sieve filter grid — filtered-out items shrink and fly into the filter chip that triggered them, the count badge pulses in sync, and the surviving items close ranks with a FLIP reflow.