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.
npx shadcn@latest add https://webberui.com/r/zoomable-density-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.
<ZoomableDensityGrid />
Installation
npx shadcn@latest add https://webberui.com/r/zoomable-density-grid.jsonOr, once registries are configured in components.json, install it as @webberui/zoomable-density-grid.
Usage
import { ZoomableDensityGrid } from "@/components/ui/zoomable-density-grid";
<ZoomableDensityGrid densities={[2, 4, 6]} defaultDensity={4} gap={8}>
{photos.map((src) => (
<img
key={src}
src={src}
alt=""
className="aspect-square rounded-lg object-cover"
/>
))}
</ZoomableDensityGrid>Every direct child is wrapped automatically in an animated motion.div cell, with no extra markup needed.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
densities | number[] | [2, 4, 6] | Density steps (each step = columns per row), shown on the switcher in order |
defaultDensity | number | densities[0] | Initial column count; must be one of the densities steps |
gap | number | 8 | Gap between cells (px) |
children | React.ReactNode | — | Grid content; every direct child is wrapped automatically in an animated cell |
onDensityChange | (density: number) => void | — | Fires when the density changes, receiving the new column count |
className | string | — | Appended to the outermost container's className |
How it works
- FLIP reflow: when the column count changes, every cell transitions its position and size continuously through Motion's
layoutanimation rather than fading out and reappearing - Scale-down spring back: at the moment of the switch, every cell takes a brief scale-down and spring back (spring), mimicking the feel of zooming in iOS Photos
- Sliding pill: the switcher's selected pill slides between steps through
layoutId - The layout animation is implemented with transforms, so cell content scales briefly during the transition; flat color blocks, images, and similar content look best
- The steps passed to
densitiesare rounded, floored at 1, and deduplicated automatically; an empty array falls back to the default[2, 4, 6]
Accessibility
- When the user has "reduce motion" enabled at the system level, the FLIP and the pill slide are disabled and a switch jumps straight to the new layout (the DOM structure matches SSR)
- The switcher is a group of native
<button>s (role="group"plusaria-label), each step marking its selected state witharia-pressedand carrying anaria-labelof "N columns per row" - The steps can be focused in order with Tab, showing an outline on
focus-visible
Paginated Wave Grid
A paginated grid layout — on a page change the outgoing items exit in distance-based waves toward the turn direction while the new page's items flood in from the opposite side, with the page number and total rolling and morphing in sync.
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.