Image Grid
An adaptive photo grid that lays out 1 to 9 images in WeChat-moments style: 1–8 use hand-tuned collage layouts on a 6×6 grid (every cell stays a healthy size) and 9 fills a uniform 3×3 grid, with the container always staying square. Responsive, dependency-free, with optional keyboard-accessible per-cell click and custom rendering for lightbox integration.
An adaptive photo grid, laid out the way social apps stack image attachments. The layout is driven entirely by how many images you pass: one to eight form hand-tuned mosaic collages on a 6×6 grid and nine fills a uniform 3×3 grid. Every layout tiles a square container so no cell collapses into a thin strip. Only the first nine images are rendered — the nine-grid cap.
It is a pure layout primitive: zero dependencies, all styling is plain
Tailwind, and the only dynamic values (the grid template and per-cell spans)
are applied inline. Images are cropped to fill their cell with object-cover.
Interaction stays out of the component — pass onImageClick for a tap target,
or renderImage to wrap each cell yourself (e.g. in a lightbox trigger).
Installation
One-time setup: add the @ikui registry to your components.json.
{
"registries": {
"@ikui": "https://ik-ui.pages.dev/r/{name}.json"
}
}Then install the component:
pnpm dlx shadcn@latest add @ikui/image-gridUsage
import { ImageGrid } from "@/components/image-grid";<ImageGrid
images={[
{ src: "/a.jpg", alt: "" },
{ src: "/b.jpg", alt: "" },
{ src: "/c.jpg", alt: "" },
]}
/>;Examples
Adaptive layouts
The same component, the same images — only the count changes. One to eight build mosaic collages; nine fills the uniform 3×3 grid. Tap a number to see each layout.
Uniform grid
Nine images fill a uniform three-column grid of square cells. gap controls
the spacing between cells.
Clickable cells
Pass onImageClick to make each cell a tap target — it receives the cell
index. Each cell then renders as a real, keyboard-operable <button>
(focusable, activated with Enter/Space) with a cursor-pointer; what happens on
click (open a lightbox, select, delete) is up to you.
Props
| Prop | Type | Default |
|---|---|---|
images | { src: string; alt?: string }[] | - |
gap | number | 4 |
onImageClick | (index: number) => void | - |
renderImage | (image, index) => ReactNode | - |
className | string | - |








