Organic gallery layouts
for React

A lightweight component that arranges images into beautiful, organic collages. No grid. No masonry. Just art.

npm install @inkorange/pendu

Why Pendu?

Organic Layouts

No grid lines, no rigid columns. Images arrange themselves into natural, gallery-wall collages that fill your container.

Animated Transitions

FLIP animations smoothly move images when the gallery changes. Add, remove, or reorder — every transition feels intentional.

Container Aware

Automatically adapts to any container size — fixed, percentage, or viewport units. Images scale and reflow to fill the space.

Tiny Footprint

~6 KB gzipped. Zero dependencies beyond React. Ships ESM and CJS with full TypeScript types.

CSS Variable Theming

Customize gap, radius, and background via CSS custom properties. No prop drilling needed.

Deterministic Seeds

Same seed + same images = identical layout. Reproducible across renders, servers, and sessions.

Get started in seconds

Basic Usage

Gallery.tsx
import { Pendu } from '@inkorange/pendu';

function Gallery() {
  return (
    <Pendu gap={12} seed={42}>
      <Pendu.Image src="/photo-1.jpg" width={1200} height={800} alt="Sunset" />
      <Pendu.Image src="/photo-2.jpg" width={800} height={1200} alt="Portrait" />
      <Pendu.Image src="/photo-3.jpg" width={1600} height={1000} alt="Landscape" />
    </Pendu>
  );
}

Dynamic Arrays

DynamicGallery.tsx
import { Pendu } from '@inkorange/pendu';
import { useState } from 'react';

function DynamicGallery({ photos }) {
  return (
    <Pendu gap={12}>
      {photos.map((photo) => (
        <Pendu.Image
          key={photo.id}
          src={photo.src}
          width={photo.width}
          height={photo.height}
          alt={photo.alt}
        />
      ))}
    </Pendu>
  );
}

CSS Variable Theming

styles.css
/* Override via CSS variables — no props needed */
.my-gallery {
  --pendu-gap: 16px;
  --pendu-radius: 8px;
  --pendu-bg: #1a1a1a;
}

Ready to build?

Explore interactive examples or dive into the API docs.