Skip to content
MIZU.

SIG—15 Signature system

Gallery

A justified photo grid whose photos open from exactly where they sit: swipe to travel, pull down to put one back, pinch or double-tap to look closer.

View .md

Live — this one is real

State

Open a plate and it lifts out of the grid. Swipe or use ← → to travel, pull down or press Esc to put it back; pinch, Ctrl-scroll or double-tap to zoom. Plates are drawn in the browser.

index · rowHeightViewer closed
Row height

Usage

tsx
import { Gallery } from "mizu-ui";

export function Example() {
  const field = (ground: string, mark: string) =>
    `data:image/svg+xml,${encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" width="720" height="360"><pattern id="d" width="36" height="36" patternUnits="userSpaceOnUse"><path d="M18 13l5 5-5 5-5-5z" fill="${mark}"/></pattern><rect width="720" height="360" fill="${ground}"/><rect width="720" height="360" fill="url(#d)"/></svg>`)}`;
  return (
    <Gallery
      label="Field studies"
      rowHeight={160}
      items={[
        {
          id: "ink",
          src: field("#141413", "#7e776b"),
          alt: "Diamond field on ink",
          width: 720,
          height: 360,
          caption: "Field, ink",
        },
        {
          id: "paper",
          src: field("#f4f0e8", "#93877a"),
          alt: "Diamond field on paper",
          width: 720,
          height: 360,
          caption: "Field, paper",
        },
      ]}
    />
  );
}

This code, running — no configuration beyond the data

Keyboard

←→
Previous or next photo, in the grid or viewer
↑↓
The row above or below, in the grid
HomeEnd
First or last photo
+−0
Zoom in, out, or back to fit
Esc
Put the photo back

Source & setup

Add this component to your project with the shadcn CLI. The command copies its source into your configured UI directory, with base.css, Mizu’s tokens, and the stylesheets that hold its rules. Each rule lives in one stylesheet, so components you add together never repeat CSS. In a project without a components.json, run npx shadcn@latest init 0xuser64bit/mizu/preset first: plain init installs shadcn’s theme into your global CSS. Then load the fonts.

sh
npx shadcn@latest add 0xuser64bit/mizu/gallery

Every component imports from its own path, its name in kebab case. To paste the example, replace its mizu-ui import with:

tsx
import { Gallery } from "@/components/ui/mizu/gallery";

For npm imports, import mizu-ui/styles.css once. Fonts are optional via mizu-ui/fonts.css. Override --mizu-* tokens or use className for local styling. Requires React and Motion. A Client Component: its source starts with “use client”.

Inspect signature/Gallery.tsx

Source is included in the npm package under src/signature/Gallery.tsx.

Open full source ↗

Props

PropTypeDefaultDescription
label / itemsstring / GalleryItem[]—Region name; each photo has an id, src, alt, width and height, with an optional thumbnail and caption.
index / defaultIndex / onIndexChangenumber | null—The open photo, or null while the viewer is closed.
rowHeight / gapnumber—Rows aim for this height (220) and full rows adjust to fill the width exactly; gap defaults to 6.
loading / emptyboolean / ReactNode—Skeleton rows with a scan line, or what to show when there are no photos.
justifyRows()(sizes, width, target, gap) => Row[]—The layout on its own, for server rendering or grids of your own.

Accessibility

Photos are buttons named by their alt text, in a list; arrow keys move between them, up and down by row. The viewer is a native modal dialog: the page goes inert and stops scrolling, Escape closes, and focus returns to the photo you closed on. Position and alt text are announced as you travel, and every gesture also has a key or a button.

Motion

A photo flies out of its tile into the viewer, and back into the tile of whichever photo you close on. Swipes follow your finger with resistance at the ends; pulling down shrinks the photo and thins the backdrop until it lets go. Zoom springs around the point you pinch, scroll or double-tap. Reduced motion trades the flight for a short fade.

Works alongside