Documentation

Everything you need to use Pendu in your React application.

Installation

npm install @inkorange/pendu
import { Pendu } from '@inkorange/pendu';

Requires React 18+ as a peer dependency. Works with Next.js, Vite, CRA, and any React setup.

<Pendu> Props

PropTypeDefaultDescription
gapnumber8Space between images in pixels
seednumber42Random seed for deterministic layouts. Same seed = same layout.
paddingnumber0Inner padding of the gallery container
classNamestringCSS class applied to the gallery root element

<Pendu.Image> Props

PropTypeDefaultDescription
srcstringrequiredImage source URL
widthnumberrequiredOriginal image width (for aspect ratio)
heightnumberrequiredOriginal image height (for aspect ratio)
altstring""Alt text for accessibility

<Pendu.Item> Props

Use <Pendu.Item> for custom content — videos, cards, CTAs, or any React component. Provide width and height for aspect ratio calculation; the layout engine handles sizing and positioning.

PropTypeDefaultDescription
widthnumberrequiredDesired width (for aspect ratio calculation)
heightnumberrequiredDesired height (for aspect ratio calculation)
childrenReactNoderequiredContent to render inside the frame
classNamestringCSS class on the item wrapper
MixedGallery.tsx
import { Pendu } from '@inkorange/pendu';

function MixedGallery() {
  return (
    <Pendu gap={12}>
      <Pendu.Image src="/photo.jpg" width={1200} height={800} alt="Photo" />

      <Pendu.Item width={400} height={300}>
        <video src="/clip.mp4" autoPlay muted loop
          style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
      </Pendu.Item>

      <Pendu.Item width={400} height={400}>
        <div style={{ padding: 24, background: '#1a1a2e', color: '#fff' }}>
          <h3>New Collection</h3>
          <p>Explore the latest additions</p>
        </div>
      </Pendu.Item>
    </Pendu>
  );
}

CSS Variables

Override styles without props by setting CSS custom properties on the gallery or any ancestor.

VariableDefaultDescription
--pendu-gap8pxOverride gap between images
--pendu-radius0Border radius on image frames
--pendu-bgtransparentBackground color of the gallery
--pendu-transition0.4s easeTransition timing for FLIP animations
styles.css
.my-gallery {
  --pendu-gap: 16px;
  --pendu-radius: 8px;
  --pendu-bg: #111;
  --pendu-transition: 0.6s cubic-bezier(0.25, 0.1, 0.25, 1);
}

<Pendu className="my-gallery" gap={16}>
  {/* images */}
</Pendu>

Dynamic Images

Pendu reacts to children changes automatically. Add or remove <Pendu.Image> or <Pendu.Item> elements and the gallery re-layouts with smooth FLIP animations. Use stable key props so React can track each element.

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

function PhotoManager() {
  const [photos, setPhotos] = useState(initialPhotos);

  const addPhoto = (photo) => {
    setPhotos(prev => [...prev, photo]);
  };

  const removePhoto = (id) => {
    setPhotos(prev => prev.filter(p => p.id !== id));
  };

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

Container Behavior

Dynamic height (default): The gallery grows vertically to fit all images. The container only needs a width.

Fixed height: When the parent has a fixed height (px, vh, %), the gallery scales the entire layout to fit both width and height constraints. More images in a smaller area means smaller images.

Responsive: Pendu observes container resizes and re-layouts automatically. No additional code needed.