Parallax Tilt Gallery
A grid of six colored tiles where each tile reacts on its own according to its distance from the pointer: within 200 px it leans toward it (5° at most, at 100 px), grows up to 5% and gets a white glint at the spot you point at — nearby tiles move a lot, distant ones barely; mouse, finger or Tab key, and flat tiles (glint kept) when the user reduces motion.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. 3D has 50 effects, including 8 free. Explore the category →
3 usage examples



How it works
The root .fx-0030-root (#0a0a0f background, at least 300 px tall) centers .tilt-gallery, a three-column grid (300 px wide at most, 10 px gaps) marked data-tilt-gallery. Its six .tilt-item are square (about 87 px), with 10 px corners, each with its own gradient (indigo, pink, green, amber, violet, red) and a white symbol ▲ ■ ◆ ★ ♥ ●. Each tile holds an empty .tilt-shine that covers it: a white radial gradient at 25% centered on --shine-x and --shine-y, invisible at rest, that appears on hover or focus.
The script looks for every [data-tilt-gallery] on the page. On each pointer move over the grid, it measures for each tile the distance between the pointer and its center, and derives an intensity that is 1 under the pointer and 0 at 200 px. The tile gets perspective(400px) rotateX(…) rotateY(…) scale(…): the side facing the pointer sinks, with an angle that grows with the offset and then falls with the intensity, so it peaks at 5° for a tile 100 px away; the tile right under the pointer barely tilts but grows by 5%. The glint of the hovered tile sits exactly under the pointer. A 0.3 s transition keeps the motion smooth; when the pointer leaves, every tile returns flat. Each tile carries its own perspective in its transform: no preserve-3d, so nothing a parent could flatten.
The script listens to pointer events: a pen or a finger makes the grid react, and touch-action: pan-y lets the page scroll vertically, only a sideways drag tilts the tiles. The tiles have tabindex="0": when one gets the focus, the script acts as if the pointer were at its center, so it grows, its glint lights up, and its neighbors lean toward it; when the focus leaves the grid, everything returns flat. In a real gallery, make each tile a link (<a>): the same code works, without tabindex.
Accessibility
- Reduced motion: under
prefers-reduced-motion: reduce, a block scoped to.fx-0030-rootforcestransform: noneon the tiles and keeps only the shadow transition. The tiles stay flat; the white glint still follows the pointer and the shadow marks the hovered tile. Measured in Chromium, Firefox and WebKit: no tilt under the mouse (4° without the preference on the measured tile). - Keyboard: Tab moves from tile to tile; the active tile grows by 5%, lights up and gets a 2 px white outline offset by 3 px on
:focus-visible, and its direct neighbors lean about 5° toward it. Measured in Chromium and Firefox: two Tab presses put the focus on the second tile, scaled to 1.05. Safari only gives Tab focus to links and fields unless the system keyboard option is turned on: use links for the tiles. - Screen readers: the symbols are read as they are ("black up-pointing triangle"…). In a real gallery, give each image a title or an
altand an explicit link; the.tilt-shineglint is empty and is not read. - Contrast: the white symbols are decorative; on the light ends of the gradients, white drops below 3:1 (1.7:1 on the amber
#fbbf24, 1.9:1 on the green#34d399). For text inside the tiles, darken the gradient or add a dark band, like the "Néons" cover in the first scene.
Browser compatibility
CSS (perspective(), rotateX, rotateY and scale in each tile's transform, CSS variables inside a gradient, touch-action, :focus-visible) + JS with pointer and focusin / focusout events, no dependencies. Measured in Chromium 151, Firefox 153 and WebKit 26.5: same angles, same glint, two independent grids on one page, flat tiles under reduced motion; finger reaction measured in Chromium in touch mode. No preserve-3d: Playwright's WebKit renders these tilts like Chromium, but a real Safari was not checked.
Without 3D transforms, the tiles grow without tilting; the glint and the shadow remain. Without :focus-visible, the browser's default focus ring shows instead.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0030-root">
<div class="tilt-gallery" data-tilt-gallery="">
<div class="tilt-item" tabindex="0">
<span class="tilt-shine"></span>▲
</div>
… (6 tiles)
</div>
</div>
One-time payment, no subscription — the code is yours forever
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Reach (JS — maxDist = 200) |
200 px | Distance beyond which a tile stops reacting. At 120 only the direct neighbors move; at 300 the whole grid ripples. |
Tilt strength (JS — the 20 factor of rotX and rotY) |
20 (5° at most, at half the reach) | The maximum angle is a quarter of this factor: 40 gives 10° for the tile 100 px from the pointer. |
Growth (JS — 0.05 in scale) |
5% | 0.1 makes the hovered tile stand out clearly; at 0 only the tilt remains. |
Perspective (JS — perspective(400px)) |
400px | Small value, strong distortion. Change it in the reset() function as well. |
Glint (CSS — .tilt-item .tilt-shine) |
rgba(255, 255, 255, .25), transparent 60% | On white tiles, switch to a tint (blue at .18 in the second scene); for a sharper glint, lower 60% to 40%. |
Grid size (CSS — max-width: 300px and grid-template-columns of .tilt-gallery) |
300 px, 3 columns | The reach is in pixels: if you make the tiles much larger, raise maxDist accordingly to keep the neighborhood effect. |
Speed (CSS — transition: transform .3s of .tilt-item) |
0.3 s | 0.15 s for lively tiles, 0.5 s for a heavier motion. |
FAQ
.tilt-item in the grid, whatever their number. The colors are set by :nth-child(1) to (6): add rules for the next ones. Each move measures every tile: for a very large grid, split it into several data-tilt-gallery grids, each gets its own instance.