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
Import
import 'motion-components/motion-gallery' // just this one
import 'motion-components' // or the whole libraryUsage
<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 |
|---|---|---|---|---|
| | | string | number | 3 | Grid column count. Accepts any valid CSS repeat() value or a plain number. |
| | | number | 16 | Gap between grid items in px |
| | | string | — | Enforces a uniform aspect ratio on all items, e.g. "4/3", "1/1", "16/9" |
| | | 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 |
|---|---|
| | Opens the lightbox on the item at index (default 0). If the lightbox is already open, moves to that item. |
| | Closes the lightbox. |
| | Moves to the next item. Does nothing on the last item or while closed. |
| | Moves to the previous item. Does nothing on the first item or while closed. |
| | Read-only. Index of the item open in the lightbox, or -1 while it is closed. |
Events
| Event | Detail | Description |
|---|---|---|
| | | Fired when the lightbox opens. |
| | | Fired when the lightbox moves to another item. |
| | — | 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.