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
| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
images | GalleryImage[] | - | Yes | Array of image objects (see GalleryImage interface below) |
variant | "default" | "masonry" | "grid" | "default" | No | Visual variant |
itemAspectRatio | "square" | "landscape" | "portrait" | "auto" | "auto" | No | Aspect ratio for gallery items |
itemHover | "none" | "zoom" | "overlay" | "zoom" | No | Hover effect for items |
showLightbox | boolean | true | No | Whether to enable lightbox on click |
lightboxAnimation | "fade" | "slide" | "zoom" | "fade" | No | Lightbox animation type |
showThumbnails | boolean | false | No | Whether to show thumbnails in lightbox |
thumbnailSize | "sm" | "md" | "lg" | "md" | No | Size of thumbnails |
onImageClick | (image: GalleryImage, index: number) => void | undefined | No | Callback when image is clicked |
className | string | undefined | No | Additional CSS classes |
itemClassName | string | undefined | No | Additional 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