A responsive masonry image grid that opens a swipeable, keyboard-navigable lightbox. Tiles keep each image's ratio, captions show in the lightbox only, and the lightbox is a fullscreen `Modal`.

pnpm add @behivetech/organisms.gallery

You are viewing Gallery v0.1.5, snapshotted 2026-11-09. The latest is v0.1.7. Go to the latest →

Examples of v0.1.5 are shown as code; only the latest version runs live. Open the live examples →

The default grid: two columns on a phone, three from 48rem and four from 64rem. Tap a tile to open the lightbox.

Basic Gallery
1const placeholder = (hue: number, width: number, height: number) =>
2 `data:image/svg+xml;utf8,${encodeURIComponent(
3 `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}"><rect width="100%" height="100%" fill="hsl(${hue} 80% 55%)"/></svg>`,
4 )}`;
5
6const SIZES = [
7 [1200, 800],
8 [800, 1200],
9 [1000, 1000],
10 [1200, 800],
11 [800, 1000],
12 [1200, 900],
13 [900, 1200],
14 [1200, 800],
15] as const;
16
17const IMAGES: GalleryImage[] = SIZES.map(([width, height], index) => ({
18 src: placeholder(index * 45, width, height),
19 alt: `Photo ${index + 1}`,
20 width,
21 height,
22 caption: index % 2 === 0 ? `Caption for photo ${index + 1}` : undefined,
23}));
24
25const BasicGallery = () => <Gallery images={IMAGES} />;
26
27render(<BasicGallery />);

A fixed number of columns at every width.

Three Columns
1const placeholder = (hue: number, width: number, height: number) =>
2 `data:image/svg+xml;utf8,${encodeURIComponent(
3 `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}"><rect width="100%" height="100%" fill="hsl(${hue} 80% 55%)"/></svg>`,
4 )}`;
5
6const SIZES = [
7 [1200, 800],
8 [800, 1200],
9 [1000, 1000],
10 [1200, 800],
11 [800, 1000],
12 [1200, 900],
13 [900, 1200],
14 [1200, 800],
15] as const;
16
17const IMAGES: GalleryImage[] = SIZES.map(([width, height], index) => ({
18 src: placeholder(index * 45, width, height),
19 alt: `Photo ${index + 1}`,
20 width,
21 height,
22 caption: index % 2 === 0 ? `Caption for photo ${index + 1}` : undefined,
23}));
24
25const ThreeColumns = () => <Gallery images={IMAGES} columns={3} />;
26
27render(<ThreeColumns />);

Columns per breakpoint: `xs` below 48rem, `md` from 48rem, `lg` from 64rem.

Responsive Columns
1const placeholder = (hue: number, width: number, height: number) =>
2 `data:image/svg+xml;utf8,${encodeURIComponent(
3 `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}"><rect width="100%" height="100%" fill="hsl(${hue} 80% 55%)"/></svg>`,
4 )}`;
5
6const SIZES = [
7 [1200, 800],
8 [800, 1200],
9 [1000, 1000],
10 [1200, 800],
11 [800, 1000],
12 [1200, 900],
13 [900, 1200],
14 [1200, 800],
15] as const;
16
17const IMAGES: GalleryImage[] = SIZES.map(([width, height], index) => ({
18 src: placeholder(index * 45, width, height),
19 alt: `Photo ${index + 1}`,
20 width,
21 height,
22 caption: index % 2 === 0 ? `Caption for photo ${index + 1}` : undefined,
23}));
24
25const ResponsiveColumns = () => (
26 <Gallery images={IMAGES} columns={{ xs: 1, md: 2, lg: 5 }} />
27);
28
29render(<ResponsiveColumns />);