Parallax Layers
Three CSS layers stacked at distinct translateZ depths inside a preserve-3d context — hovering deepens each plane and reveals the depth illusion.
You'll get access to the interactive demo with a free account.
This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Cards has 41 effects, including 6 free. Explore the category →
3 usage examples



How it works
The .card-parallax-layers container combines two CSS properties that open a shared 3D space: transform-style: preserve-3d tells the rendering engine to place child elements inside the same three-dimensional volume (instead of flattening them), while perspective: 1000px sets the virtual eye distance. The lower this value, the more intense the perspective distortion.
Each child layer is placed at its own Z depth via translateZ: the background layer at 20 px, the mid layer at 40 px, and the foreground layer at 60 px. CSS perspective projection makes closer elements appear slightly larger — this scale differential between the three planes creates the depth illusion even without motion.
On :hover, each layer advances an additional 10 px in Z (20→30, 40→50, 60→70). The transition: transform .3s animates this change. Because the foreground layer is already the closest to the viewpoint, the perspective enlargement it undergoes during this advance is greater than the background's — that enlargement differential is what produces the parallax feeling between layers.
The effect is entirely declarative CSS — no JavaScript, no requestAnimationFrame, no mousemove listener. The card does not tilt based on cursor position: only the global hover triggers the Z transition. Result: zero JS computation cost, compatible with SSR and strict CSP.
Accessibility
- prefers-reduced-motion absent from the code: CSS transitions always play on hover, even if the user has enabled 'Reduce Motion' in their OS. For an accessible implementation, wrap the
transitiondeclarations inside@media (prefers-reduced-motion: no-preference) { … }. - No ARIA roles (
role,aria-label) are present on the card in the base code — layers are treated as decorative. If layer content is informative, addrole="img"and anaria-labelon the container. - The trigger is
:hoveronly. A keyboard user tabbing to the card does not activate the 3D effect. To fix this, add.card-parallax-layers:focus-within .layer-N { transform: translateZ(…) }mirroring each:hoverrule. - The
layer-1element has an opacity of 0.3 in the provided example — if it contains informative text or an icon, the contrast ratio may be insufficient (WCAG 1.4.3). Verify contrast or use this layer for decorative elements only. - The effect is purely visual and does not interfere with screen readers as long as text content stays in the normal DOM flow (layers are not
aria-hiddenin the base code).
Browser compatibility
Requires transform-style: preserve-3d and perspective — natively supported in all modern browsers since 2015.
Without transform-style: preserve-3d support, layers render flat in DOM order — content stays readable and the card functional; the depth effect does not appear.
The code
HTML structure to paste into your page (CSS + JS available with a premium account):
<div class="card-demo card-parallax-layers">
<!-- Background layer: decorative element, texture, image (lowest translateZ) -->
<span class="layer-1" style="position: absolute;">…</span>
<!-- Mid layer: badge, price, category -->
<span class="layer-2" style="position: absolute;">…</span>
<!-- Foreground layer: main title, CTA (highest translateZ) -->
<span class="layer-3">…</span>
</div>
Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
| --bg-card | #12121a | CSS variable on :root — card background color. Accepts any CSS value (color, gradient via background-image, transparent). |
| --radius-xl | 1rem | CSS variable on :root — card corner radius. |
| perspective | 1000px | CSS property on .card-parallax-layers — virtual eye distance. 500 px = exaggerated dramatic effect; 1500 px+ = subtle and elegant perspective. |
| translateZ (layer-1) | 20px / hover 30px | Background layer depth, declared in .card-parallax-layers .layer-1 and :hover .layer-1. Increase the rest/hover gap to accentuate the animation. |
| translateZ (layer-2) | 40px / hover 50px | Mid layer depth in .card-parallax-layers .layer-2 and :hover .layer-2. |
| translateZ (layer-3) | 60px / hover 70px | Foreground layer depth in .card-parallax-layers .layer-3 and :hover .layer-3. This is the most visible plane — reserve it for your title or primary element. |
| transition | .3s | Transition duration on each layer. Add an explicit easing (e.g. ease-out, cubic-bezier(.2,.6,.3,1)) to customise the depth cinematics. |
FAQ
:hover — all layers advance in Z without any rotation or tilt based on cursor position. To add tilt (rotX/rotY computed on mousemove), about ten lines of JS are enough: listen to mousemove on the card, normalise the cursor offset between -1 and 1, then apply rotateX(Ydeg) rotateY(Xdeg) on the container directly via style.transform..card-parallax-layers and define its depth in CSS: .card-parallax-layers .layer-4 { transform: translateZ(80px); transition: transform .3s } with its corresponding hover state. There is no technical limit, but beyond 4 or 5 layers the perceived depth saturates visually and the closest layers may overflow the perspective container boundaries.:hover is rarely triggered by touch (behaviour depends on the browser and OS). The card displays statically with layers at their resting depth — no error, just no animation. To activate the effect on touch, add a touchstart/touchend JS handler that adds and removes an .is-hovered CSS class on the card, mirroring the :hover rules.