Skip to Content
ComponentsGallery

Gallery

The Gallery component provides you with image galleries and media showcases.

Live Examples

Live Example
☀️
Copy
View Code
import { Gallery } from '@jarllyng/nostromo' export default function GalleryExample() { return ( <Gallery images={[ { id: 'img1', src: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='300'%3E%3Crect width='100%25' height='100%25' fill='%23e4e4e7'/%3E%3Ctext x='50%25' y='50%25' font-family='sans-serif' font-size='14' fill='%2371717a' text-anchor='middle' dy='.35em'%3EImage%201%3C/text%3E%3C/svg%3E", alt: "Image 1" }, { id: 'img2', src: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='300'%3E%3Crect width='100%25' height='100%25' fill='%23e4e4e7'/%3E%3Ctext x='50%25' y='50%25' font-family='sans-serif' font-size='14' fill='%2371717a' text-anchor='middle' dy='.35em'%3EImage%202%3C/text%3E%3C/svg%3E", alt: "Image 2" }, { id: 'img3', src: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='300'%3E%3Crect width='100%25' height='100%25' fill='%23e4e4e7'/%3E%3Ctext x='50%25' y='50%25' font-family='sans-serif' font-size='14' fill='%2371717a' text-anchor='middle' dy='.35em'%3EImage%203%3C/text%3E%3C/svg%3E", alt: "Image 3" }, { id: 'img4', src: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='300'%3E%3Crect width='100%25' height='100%25' fill='%23e4e4e7'/%3E%3Ctext x='50%25' y='50%25' font-family='sans-serif' font-size='14' fill='%2371717a' text-anchor='middle' dy='.35em'%3EImage%204%3C/text%3E%3C/svg%3E", alt: "Image 4" },...
Live Example
☀️
Copy
View Code
import { Gallery } from '@jarllyng/nostromo' export default function GalleryMasonryExample() { return ( <Gallery spacing="lg" images={[ { id: 'img7', src: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='500'%3E%3Crect width='100%25' height='100%25' fill='%23e4e4e7'/%3E%3Ctext x='50%25' y='50%25' font-family='sans-serif' font-size='14' fill='%2371717a' text-anchor='middle' dy='.35em'%3ETall%201%3C/text%3E%3C/svg%3E", alt: "Tall Image 1" }, { id: 'img8', src: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='300'%3E%3Crect width='100%25' height='100%25' fill='%23e4e4e7'/%3E%3Ctext x='50%25' y='50%25' font-family='sans-serif' font-size='14' fill='%2371717a' text-anchor='middle' dy='.35em'%3EWide%201%3C/text%3E%3C/svg%3E", alt: "Wide Image 1" }, { id: 'img9', src: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='400'%3E%3Crect width='100%25' height='100%25' fill='%23e4e4e7'/%3E%3Ctext x='50%25' y='50%25' font-family='sans-serif' font-size='14' fill='%2371717a' text-anchor='middle' dy='.35em'%3ESquare%201%3C/text%3E%3C/svg%3E", alt: "Square Image 1" },...
Live Example
☀️
Copy
View Code
import { Gallery } from '@jarllyng/nostromo' export default function GalleryGridExample() { return ( <Gallery spacing="md" images={[ { id: 'img12', src: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Crect width='100%25' height='100%25' fill='%23e4e4e7'/%3E%3Ctext x='50%25' y='50%25' font-family='sans-serif' font-size='14' fill='%2371717a' text-anchor='middle' dy='.35em'%3E1%3C/text%3E%3C/svg%3E", alt: "Image 1" }, { id: 'img13', src: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Crect width='100%25' height='100%25' fill='%23e4e4e7'/%3E%3Ctext x='50%25' y='50%25' font-family='sans-serif' font-size='14' fill='%2371717a' text-anchor='middle' dy='.35em'%3E2%3C/text%3E%3C/svg%3E", alt: "Image 2" }, { id: 'img14', src: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Crect width='100%25' height='100%25' fill='%23e4e4e7'/%3E%3Ctext x='50%25' y='50%25' font-family='sans-serif' font-size='14' fill='%2371717a' text-anchor='middle' dy='.35em'%3E3%3C/text%3E%3C/svg%3E", alt: "Image 3" },...

Import

import { Gallery } from "@jarllyng/nostromo"; // or import { Gallery } from "@jarllyng/nostromo/gallery";

Usage

import { Gallery } from "@jarllyng/nostromo"; export default function Example() { return ( <Gallery images={[ { id: "img1", src: "/photos/hangar.jpg", alt: "The hangar bay" }, { id: "img2", src: "/photos/bridge.jpg", alt: "The bridge" }, ]} /> ); }

Code Examples

Props

PropTypeDefaultRequiredDescription
imagesGalleryImage[]-YesArray of image objects (see GalleryImage interface below)
variant"default" | "masonry" | "grid""default"NoVisual variant
itemAspectRatio"square" | "landscape" | "portrait" | "auto""auto"NoAspect ratio for gallery items
itemHover"none" | "zoom" | "overlay""zoom"NoHover effect for items
showLightboxbooleantrueNoWhether to enable lightbox on click
lightboxAnimation"fade" | "slide" | "zoom""fade"NoLightbox animation type
showThumbnailsbooleanfalseNoWhether to show thumbnails in lightbox
thumbnailSize"sm" | "md" | "lg""md"NoSize of thumbnails
onImageClick(image: GalleryImage, index: number) => voidundefinedNoCallback when image is clicked
classNamestringundefinedNoAdditional CSS classes
itemClassNamestringundefinedNoAdditional CSS classes for items

GalleryImage Interface

interface GalleryImage { src: string; alt: string; title?: string; description?: string; thumbnail?: string; }

The lightbox is a real modal

It is a Radix Dialog, which means the things aria-modal="true" promises are actually done:

  • Focus moves into the lightbox when it opens, and cannot leave it by Tab while it is open.
  • Escape closes it, from the moment it opens, without needing a click first.
  • The arrow keys move between images for the same reason.
  • The page behind it is out of the accessibility tree and cannot be scrolled.
  • Closing returns focus to the gallery item that opened it, not to the top of the page.

The dialog is named from the image’s title, falling back to its alt, and the id is generated per instance. Two galleries on one page no longer share one.

None of this needed configuring before either. It simply did not happen: focus stayed on the gallery item behind the overlay, and the key handlers were bound to an element that never had focus, so Escape did nothing.

Last updated on