Skip to article

Gallery

An image and video gallery for React, with 2D and 3D arrangements, gestures and selection that all transition smoothly into each other.

Gallery is a React component for creating dynamic image and video galleries in 2D and 3D.

Lay items out as a list, grid, barrel, coverflow, helix, ring, sphere or card stack, then drag, scroll or key through them.

Change the arrangement, open an item or add and remove items, and every change animates smoothly from wherever the gallery is.

Gallery: PlaygroundOpen example
<Gallery arrange={barrel()} aria-label="Collection">
  <Gallery.Image src="/a.jpg" alt="Runner" width={1200} height={1600} />
  <Gallery.Video src="/b.mp4" poster="/b.jpg" alt="Shoe rotating" />
</Gallery>

Features

  • Eight arrangements: list, grid, barrel, coverflow, helix, ring, sphere and stack, or write your own with defineGalleryArrangement.

  • Smooth everything: Arrangement swaps, opening an item, adding and removing items all blend from the current pose, with springs and stagger.

  • Inputs: Drag with momentum, wheel and keyboard navigation out the box, with snapping and infinite looping.

  • Detail views: Open an item into its own box with a thumbnail strip, or reveal a panel around it. Higher-res assets are loaded automatically.

  • Velocity effects: tilt and ripple modifiers link items to the gallery's speed.

  • Shaders: Draw items with WebGPU through shaders like depthOfField and reflection. Without WebGPU, the DOM draws them as normal.

  • Accessible: Uses the correct ARIA tags, respects reduced motion and supports keyboard navigation.

  • Performant: All items animated with transform, and only visible media loads and plays.

Install

First, install Motion UI in your project. You need to be a Motion+ member to generate an access token.

Usage

Import

Import Gallery from "motion-plus/gallery":

import { Gallery } from "motion-plus/gallery"

Arrangements, modifiers and presence presets are separate imports, so only the ones you use are bundled:

import { Gallery, barrel, tilt } from "motion-plus/gallery"

Items

Gallery accepts three kinds of item as children.

  • Gallery.Image renders an img. alt is required. Use "" for decorative images.

  • Gallery.Video renders a video. alt is required and is applied as aria-label.

  • Gallery.Item renders an li with any content inside.

<Gallery aria-label="Studio work">
  <Gallery.Image
    src="/photo.jpg"
    alt="Dancer mid-turn"
    width={800}
    height={1000}
  />
  <Gallery.Video src="/clip.mp4" poster="/clip.jpg" alt="Product turntable" />
  <Gallery.Item>
    <h2>Any content</h2>
  </Gallery.Item>
</Gallery>

Each item can take a value. This is its identity for active, selected, callbacks and hooks. Without one, items are identified by their index.

<Gallery.Image value="runner" src="/runner.jpg" alt="Runner" />

Size

Give the gallery a size with CSS, and its items a size with the --gallery-item-width and --gallery-item-height CSS variables. Items default to 240px by 320px.

.gallery {
  height: 100vh;
}

.gallery [data-gallery-item] {
  --gallery-item-width: 300px;
  --gallery-item-height: 400px;
}

The engine owns each item's position, size, transform and opacity. Everything else, like border-radius, background and filter, is yours to style.

Arrangements

Pass an arrangement to arrange. The default is list().

<Gallery arrange={helix({ itemsPerTurn: 8 })} />

Change arrange at any time and the gallery animates into the new shape.

list

Items along a straight track: horizontal, vertical (axis="y") or diagonal (angle).

Gallery: ListOpen example
OptionDefaultDescription
angle0Direction of the track in degrees. -30 gives a rising diagonal.
curl0Degrees of rotateY per item away from focus.
depth0Pixels pushed back per item away from focus, either side.
recede0Pixels pushed back per item along the track, like a staircase into the page.
turn0rotateY in degrees for every item.
wave0Amplitude in pixels of a sine wave across the track.
room24Pixels kept clear around an item opened inline.
<Gallery arrange={list({ angle: -30, curl: 20, depth: 40, wave: 30 })} />

grid

An infinite, pannable plane. When looping, which is the default, it's tiled with clones so its edge is never visible.

Gallery: GridOpen example
OptionDefaultDescription
columns"auto"Columns in the repeating block. "auto" fits the viewport.
fill"sequence"How the plane repeats when the item count doesn't fill a block: "sequence", "offset" or "none".
rotateX0Leans the plane back, in degrees.
rotateY0Brings the plane's left side forward, in degrees.
rotateZ0Turns the plane clockwise, in degrees.
bulge0Pixels the plane bulges towards the viewer at focus.
<Gallery arrange={grid({ columns: 6, fill: "offset", rotateX: 20, bulge: 120 })} />

barrel

Items around a cylinder. axis="y" gives a vertical barrel. A barrel always loops.

Gallery: BarrelOpen example
OptionDefaultDescription
radius"auto"Cylinder radius in pixels. "auto" spaces items by their size plus gap.
rotateX-8Camera tilt in degrees. Negative views the barrel from above.
<Gallery arrange={barrel({ radius: 600, rotateX: -12 })} axis="y" />

coverflow

The classic: a flat focused item with angled, overlapping neighbours. Coverflow spaces items with its own options, so it ignores gap.

Gallery: CoverflowOpen example
OptionDefaultDescription
turn55rotateY of side items, in degrees.
spread0.3Spacing between side items, as a fraction of item width.
depth0.5How far side items sit behind the focused one, as a fraction of width.
centerSpacing0.8Spacing between the focused item and each neighbour, as a fraction of width. From 1.05 it's a plain slide.
<Gallery arrange={coverflow({ turn: 45, spread: 0.25, depth: 0.6, centerSpacing: 0.9 })} />

helix

A spiral. The default axis is "y", which gives a vertical spiral. axis="x" gives a horizontal corkscrew.

Gallery: HelixOpen example
OptionDefaultDescription
radius"auto"Radius in pixels. "auto" spaces a turn's items by their size plus gap.
itemsPerTurn10Items per full turn.
pitch"auto"Pixels travelled along the axis per item. "auto" is 22% of an item.
<Gallery arrange={helix({ radius: 500, itemsPerTurn: 12, pitch: 48 })} />

ring

Items around a flat circle, like a dial. An arc under 360 fans them out.

Gallery: RingOpen example
OptionDefaultDescription
radius"auto"Circle radius in pixels.
arc360Degrees of arc to spread items over. A full ring always loops.
uprightfalseKeep items upright instead of rotating them around the circle.
flipfalseCurve towards the other side of the track.
rotateX0Camera tilt in degrees.
<Gallery arrange={ring({ arc: 120, upright: true, flip: true, rotateX: 10 })} />

sphere

Items on a sphere. Dragging spins it both ways. A sphere always loops.

Gallery: SphereOpen example
OptionDefaultDescription
radius"auto"Radius in pixels. "auto" is the smallest sphere where neighbours stay gap apart.
gapThe gallery's gapPixels between neighbouring items.
face"camera""camera" keeps items facing the viewer. "outward" lies them flat on the surface.
<Gallery arrange={sphere({ radius: 700, gap: 12, face: "outward" })} />

stack

A deck of cards. Swipe the top card away to reveal the next. A stack spaces items with its own options, so it ignores gap.

Gallery: StackOpen example
OptionDefaultDescription
peek14Pixels each card behind the top one peeks out above it.
depth50Pixels each card behind the top one is pushed back.
visible4Number of cards visible behind the top card.
<Gallery arrange={stack({ peek: 20, depth: 60, visible: 3 })} />

Every numeric arrangement option, apart from grid's columns, also accepts a motion value, so it can be animated or linked to scroll without a re-render.

const rotateX = useMotionValue(-8)

return <Gallery arrange={barrel({ rotateX })} />