motion-gallery

CSS grid gallery with spring hover lift and FLIP-based lightbox expand. Click any item. It springs to full screen and back. Navigate with arrows or keyboard.

Preview

01
02
03
04
05
06

Import

import 'motion-components/motion-gallery'   // just this one
import 'motion-components'               // or the whole library

Usage

<motion-gallery columns="3" gap="16" aspect-ratio="4/3">
  <img src="photo-1.jpg" alt="…" data-caption="Optional caption" />
  <img src="photo-2.jpg" alt="…" />
  <img src="photo-3.jpg" alt="…" />
</motion-gallery>

Captions

Add data-caption to any item and it will appear below the expanded view.

<motion-gallery columns="3" gap="16">
  <img src="photo.jpg" alt="…" data-caption="A beautiful sunset" />
</motion-gallery>

Aspect ratio

Set aspect-ratio on the gallery to enforce a uniform size across all grid items (useful for photo grids).

<motion-gallery columns="4" gap="8" aspect-ratio="1/1">
  …
</motion-gallery>

How it works

On click, the component records the item's getBoundingClientRect(): the First position in FLIP terminology. A fixed-position clone springs to a centered, aspect-ratio-preserved target while a blurred backdrop fades in. Navigation fades out the current clone and springs the next item in from its grid position. On collapse, the spring reverses to the original rect.

Because expand, navigate, and collapse all use the same spring clone, interrupting mid-animation is naturally smooth.

Properties

Attribute Property Type Default Description
columns columns string | number 3 Grid column count. Accepts any valid CSS repeat() value or a plain number.
gap gap number 16 Gap between grid items in px
aspect-ratio aspectRatio string — Enforces a uniform aspect ratio on all items, e.g. "4/3", "1/1", "16/9"
stagger stagger boolean true Set to "false" to disable the entrance stagger animation

Dismiss & navigate

Click the expanded item, the backdrop, or the ✕ button to collapse. Use the ← → buttons or arrow keys to move between items.

Methods

Method Description
open(index) Opens the lightbox on the item at index (default 0). If the lightbox is already open, moves to that item.
close() Closes the lightbox.
next() Moves to the next item. Does nothing on the last item or while closed.
prev() Moves to the previous item. Does nothing on the first item or while closed.
index Read-only. Index of the item open in the lightbox, or -1 while it is closed.

Events

Event Detail Description
motion-open index Fired when the lightbox opens.
motion-change index Fired when the lightbox moves to another item.
motion-close — Fired when the lightbox closes.

All three events bubble and are composed. Other components also fire a bubbling motion-change, so a listener on a parent element should check event.target.

import type { MotionGallery } from 'motion-components'

const gallery = document.querySelector<MotionGallery>('motion-gallery')!

document.querySelector('#open')!.addEventListener('click', () => gallery.open(0))
document.querySelector('#next')!.addEventListener('click', () => gallery.next())

gallery.addEventListener('motion-change', (event) => {
  if (event.target !== gallery) return
  console.log('now showing', event.detail.index)
})

Accessibility

When prefers-reduced-motion is set, expand and collapse are instant. The backdrop and UI still appear normally.