Components
Image Gallery
BetaA grid of clickable thumbnail images that opens a full-screen lightbox. Used for hotel photo galleries, destination photography, and room detail views.
Purpose
ImageGallery presents a collection of photos in a scannable grid and opens a full-screen lightbox when any thumbnail is clicked. It is used wherever a set of images need to be browsable beyond the primary hero photo:
- Hotel detail pages — 6-12 photos of the property, rooms, pool, restaurant
- Destination pages — editorial photography of landscapes and experiences
- Room type selection — multiple angles of a specific room category
- Activity pages — photos of what the experience looks like in practice
The lightbox supports keyboard navigation (arrow keys, Escape) and shows an image counter.
Usage
Reach for ImageGallery when you have a fixed set of standalone photos that benefit from a full-screen zoomed view — a hotel's rooms, a destination's landscapes. When images are meant to auto-advance or be browsed one-at-a-time inline within the page flow (e.g. a hero rotation), use Carousel instead; it does not open a lightbox.
ImageGallery typically sits within hotel and destination detail pages, below the hero image and description, as a dedicated "Photos" section.
Portal rendering note: The lightbox uses
createPortalto render todocument.body. For this reason, the<ImageGallery>component cannot be shown as a live interactive demo in this documentation — the previews above show static mockups. In your application it renders and works correctly.
Anatomy
- 1Thumbnail grid — 4:3 aspect ratio thumbnails in a configurable columns grid. Hover reveals a zoom icon overlay.
- 2Zoom overlay — Dark overlay on hover with ZoomIn icon. Signals clickability.
- 3Lightbox backdrop — Full-screen black/90 overlay. Renders in a portal on document.body.
- 4Lightbox image — object-fit: contain image centred in the viewport. Max 80vh tall.
- 5Image counter — "1 / 6" in a dark pill top-left of the lightbox.
- 6Navigation buttons — Circular white/10 buttons on left and right for prev/next. Disabled at first/last image.
- 7Close button — Top-right X button with white/10 background. Keyboard focus managed.
- 8Caption — Optional text below the lightbox image from image.caption.
Variants
2-column grid
4-column grid
States
| Element | State | Appearance |
|---|---|---|
| Thumbnail | Default | Image fills tile, rounded corners |
| Thumbnail | Hover | Dark overlay + ZoomIn icon |
| Thumbnail | Focus | Teal focus ring (2px) |
| Lightbox prev | Default | White/10 circle button |
| Lightbox prev | Disabled (first) | 30% opacity |
| Lightbox nav | Focus | White focus ring |
Best practices
Write descriptive alt text for every image that communicates the visual content to screen reader users.
'Overwater villa with steps into the lagoon at sunrise' describes the scene, not just the subject.
Use filename or destination-name-only strings as alt text.
'Maldives photo 3' is the filename, not the content — it conveys nothing useful to a screen reader user.
Curate the image set to 6-12 photos showing the hotel from different perspectives (bedroom, pool, restaurant, view).
6-12 photos is the ideal range for hotel galleries — enough variety, not overwhelming.
Load all available hotel photos (potentially 40+) into a single ImageGallery without grouping.
40+ thumbnails in a grid become a wall of imagery — consider pagination or category tabs instead.
Order images with the most aspirational or representative photo first.
The leading image should be the most visually compelling — it is what users click first.
Start a hotel gallery with a floor plan or site map.
Leading with a functional room plan or technical diagram when emotional photography is available misses the primary conversion moment.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| images* | Array<{ src: string; alt: string; caption?: string }> | — | Array of image objects. caption appears below the image in the lightbox. |
| columns | 2 | 3 | 4 | 3 | Number of columns in the thumbnail grid. |
| layout | 'grid' | 'grid' | Gallery layout type. Only grid is currently supported. |
| showThumbnails | boolean | true | Reserved for future thumbnail strip variant inside the lightbox. |
| className | string | — | Additional Tailwind classes on the grid container. |
Accessibility
Thumbnail buttons
Each thumbnail is a <button> with aria-label="View image N: [alt text]". The role="listitem" on each slide and role="list" on the grid provide list semantics.
Lightbox dialog
The lightbox has:
<div
role="dialog"
aria-modal="true"
aria-label={`Image ${index + 1} of ${total}: ${image.alt}`}
>
aria-modal="true" tells screen readers to ignore content outside the dialog.
Keyboard navigation in lightbox
| Key | Action |
|---|---|
Escape | Close lightbox |
ArrowLeft | Previous image |
ArrowRight | Next image |
Tab | Navigate: close button → prev → next |
Focus management
When the lightbox opens, focus moves into the dialog. When closed, focus returns to the thumbnail that opened it (the trigger index is tracked in state).
Scroll lock
The lightbox does not currently lock body scroll. If your layout requires scroll lock when the lightbox is open, add document.body.style.overflow = 'hidden' via a useEffect in the consuming component.
Related components
- Carousel — use for auto-advancing or swipeable inline image browsing rather than a clickable grid with a lightbox.