Pendu - Organic Gallery Layouts for React
Product

Pendu - Organic Gallery Layouts for React

An organic gallery layout for React. Arrange images into beautiful, natural collages with no grid or masonry. 5.4 KB gzipped, zero dependencies, FLIP animations, and SSR ready.

Every gallery layout library I’ve used makes the same assumption: your images belong in a grid. Rows and columns. Maybe masonry if you’re feeling adventurous. And that’s fine for a lot of use cases, but it’s never felt right to me. If you’ve ever arranged photos on a wall, or laid out prints for a portfolio review, you know the result doesn’t look like a spreadsheet. It looks like a collage. That’s the problem Pendu solves.

Pendu breaks the linear grids and flex containers for a more fluid layout

Pendu breaks the linear grids and flex containers for a more fluid layout

Pendu is a React component that arranges images (and any other content) into organic, free-form layouts. No rows. No columns. No grid lines. You give it a set of images with their dimensions, and it fills your container with something that actually looks like it was composed by a human. The name comes from the French word for “hung,” as in pictures hung on a gallery wall.

How it works

The API is intentionally simple. You wrap your images in a Pendu component, pass each one its natural width and height, and the layout algorithm handles the rest. Here’s the basic setup:

1<Pendu gap={12} seed={42}>
2 <Pendu.Image src="/sunset.jpg" width={1200} height={800} alt="Sunset" />
3 <Pendu.Image src="/portrait.jpg" width={800} height={1200} alt="Portrait" />
4 <Pendu.Image src="/cityscape.jpg" width={1600} height={1000} alt="City" />
5</Pendu>

The seed prop is one of my favorite details. Same seed plus same inputs equals an identical layout every time. Deterministic across renders, across servers, across sessions. So you get the organic feel without the randomness being actually random in production. You can also omit it and let each render produce a fresh arrangement.

Not just images

Customize the items within each container

Customize the items within each container

I knew early on that a gallery component limited to <img> tags wouldn’t be enough. Pendu ships with a Pendu.Item wrapper that accepts any React children. Videos, promotional cards, interactive elements, whatever you need. You just provide a width and height for the aspect ratio, and Pendu treats it like any other frame in the layout. I’ve been using it to mix product cards in alongside photos and it works surprisingly well.

The Animation Layer

When you add or remove items from a Pendu gallery, it doesn’t just snap to the new layout. It uses FLIP animations to smoothly transition every frame to its new position. This was important to me because galleries are often dynamic. Users filter, sort, or paginate through images, and the transition between states should feel intentional, not jarring.

The animation is on by default but you can turn it off with animate={false} or adjust the duration. I kept the defaults conservative (300ms) because I’d rather the animation feel snappy than cinematic.

Container Awareness

One design decision I’m glad I made early: Pendu observes its container rather than the viewport. If the container has a width but no fixed height, Pendu grows vertically to fit all the content. If the container has both a fixed width and height, it scales everything to fit within those bounds. This makes it genuinely flexible. You can drop it into a sidebar, a modal, a full-bleed hero section, and it just adapts.

It uses a ResizeObserver under the hood, so if your container changes size (browser resize, sidebar toggle, whatever), the layout reflows automatically.

CSS Variable Theming

I didn’t want theming to require prop drilling or a theme provider. Pendu uses CSS custom properties for all its visual controls: --pendu-gap, --pendu-frame-radius, --pendu-bg, --pendu-transition-duration, and a few others. Set them on the gallery element or any ancestor and they cascade down. This plays nicely with dark mode toggles, responsive breakpoints, or any existing design token system.

Keeping it Small

Bundle size was a priority from the start. Pendu ships at 5.4 KB gzipped. Zero runtime dependencies. Six files total in your node_modules. React is the only peer dependency. I was pretty deliberate about this because I’ve seen too many “simple” UI components pull in half the npm registry. You install one gallery widget and suddenly you’re shipping 200 KB of utility functions you never asked for.

The package exports both ESM and CJS with full TypeScript types, and it works with Next.js App Router and React Server Components out of the box. I tested it against a few different SSR setups during development to make sure there weren’t any hydration surprises.

Why I Built It

I’ve been building component libraries and design systems for most of my career, and the tools I reach for most are the ones that solve a specific, bounded problem without trying to be a framework. Pendu came out of a real need on a personal project where I was displaying a mix of landscape and portrait photography and every layout option I tried looked mechanical. CSS Grid gave me precision but no personality. Masonry was better but still felt like a column layout wearing a disguise. I wanted the images to feel curated, not sorted.

The project also gave me a chance to dig into layout algorithms in a way I hadn’t before. The core question is deceptively simple: how do you fill a 2D space with rectangles of varying aspect ratios so the result looks balanced but not uniform? The approach Pendu takes isn’t a bin-packing algorithm in the traditional sense. It’s closer to a recursive subdivision with weighted randomness, which is why the seed matters and why the same inputs always produce the same output. There’s something satisfying about a layout that looks organic but is actually fully deterministic.

Try it Out

Pendu is published on npm and the source is on GitHub. There’s a playground where you can drag a slider to add or remove images and watch the layout reflow in real time. If you’re building anything that displays visual content and you’re tired of grids, give it a shot.