Mischief

40 / Blocks

Image Gallery

A responsive image collection with equal and masonry layouts, plus a lightbox that handles focus, keyboard navigation, and scroll locking.

From the archive

6

Installation

Copy the source into your project, or keep it behind a package.

npx shadcn@latest add Tinkerers-Labs/mischief-ui/image-gallery
import { ImageGallery } from "mischief-ui/image-gallery"
Also installs
  • @base-ui/react
  • lucide-react

Or paste it in yourself. The source imports the shared cn helper from @/lib/utils, so point that at your own copy.

registry/default/image-gallery/image-gallery.tsx
"use client" import * as React from "react"import { Grid2X2, Images, Rows3 } from "lucide-react" import { cn } from "@/lib/utils"import {  ImageGrid,  type GalleryImage,  type ImageGridLayout,} from "@/registry/default/image-grid/image-grid"import { Lightbox } from "@/registry/default/lightbox/lightbox" export type ImageGalleryItem = GalleryImage

Usage

const images = [
  {
    id: "studio",
    src: "/photos/studio.jpg",
    alt: "Sunlight across the studio table",
    width: 1600,
    height: 1200,
    caption: "The studio",
  },
]

export function WorkGallery() {
  return <ImageGallery images={images} title="Recent work" />
}

Grid or masonry

The grid gives every image the same cell, which is the right choice when the pictures are alike and comparison matters. Masonry uses CSS columns and lets each image keep its own height, which suits a mixed set where cropping would be a loss.

Masonry fills one column top to bottom before starting the next, so the visual order runs down rather than across. Where sequence carries meaning -- pages of a document, steps in order -- use the grid, because the reading order people expect and the order they are laid out in will not match.

Dimensions and loading

Give width and height wherever you know them. They reserve the right space before the image arrives, so the gallery does not reflow underneath the reader as pictures load -- and in masonry, so the columns do not rebalance twice.

Everything below the fold should stay lazy. Set loading to "eager" only for the first row or two, which are the ones the reader is waiting on.

Base UI supplies the lightbox dialog, with its focus trap, scroll lock, Escape handling, and focus restoration, so this component is imported from its own entry and needs @base-ui/react installed.

API

imagesImageGalleryItem[]Image sources, alt text, captions, and optional downloads.
titleReactNodeThe heading above the collection.
layout"grid" | "masonry"The layout when controlled.
defaultLayout"grid" | "masonry"The initial uncontrolled layout.
onLayoutChange(layout) => voidRuns when the layout changes.
selectedIdstring | nullThe open image when controlled.
onSelectedIdChange(id) => voidRuns when the lightbox opens, moves, or closes.
showLayoutTogglebooleanShows or hides the layout control.
emptyStateReactNodeContent shown when the collection is empty.
renderImage(image, context) => ReactNodeUses a framework image component or another custom renderer.

Accessibility

Every thumbnail is a named button. Base UI supplies the modal dialog, focus trap, scroll lock, Escape handling, and focus restoration. Left and Right Arrow move between images. Captions, position, and close controls remain available without hover.