Depth Card
A 220 × 160 px card made of four planes set at different depths — glowing ring at −20 px, gradient background at 0, text at 30 px, icon badge at 60 px —; on hover, tap or keyboard focus, it turns (−15° and 10°) and its planes spread further (badge at 80 px, ring at −40 px): an exploded view where the icon clearly lifts off the rest, in 3D CSS, no JavaScript.
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-0022-root (#0a0a0f background, at least 300 px tall) centers .depth-card-container, which carries perspective: 800px, then the card .depth-card: 220 × 160 px, transform-style: preserve-3d, 0.5 s transition, tabindex="0". It holds four planes, each with its own translateZ: .depth-card-ring, a 2 px rgba(139, 92, 246, .3) frame overflowing by 8 px, at −20 px; .depth-card-bg, the #312e81 → #4c1d95 gradient background, at 0; .depth-card-content, the title and the text line, at 30 px; .depth-card-icon, a 50 × 50 px violet badge with its colored shadow, at 60 px, in the top left corner.
At rest, the card faces you: depth only shows through the perspective. The badge, 60 px closer, displays at 108% (54 px measured) and drifts outward; the ring, further back, shrinks a little. On hover (or :focus-visible), the card takes rotateY(-15deg) rotateX(10deg): the four planes turn together, and the gaps between them become visible offsets — the text slides over the background, the badge comes off it. At the same time the badge moves forward to 80 px and the ring back to −40 px, in 0.5 s. Measured: 6.6% of pixels change on hover in Chromium 151, 6.4% in Firefox 153, with matching renders (1% apart).
Everything rests on preserve-3d on the card: without it, the four planes would be painted flat into the card and turn like a picture. That is why nothing on .depth-card may force flattening: no overflow: hidden, filter or opacity below 1. The text line at 70% opacity is fine: it is a final plane with no 3D children. The rotation is fixed, triggered by hover: the card does not follow the mouse, unlike Perspective Tilt (fx-0003).
Accessibility
- Reduced motion: under
prefers-reduced-motion: reduce, a block scoped to.fx-0022-rootkeeps the card facing you and the planes in place; on hover or focus, the ring lights up (rgba(167, 139, 250, .9)). Measured in Chromium and Firefox: transform unchanged, 1.2% of pixels change (6.6% without the preference). - Keyboard: the card has
tabindex="0"; focus gives the hover's exploded view, plus a 2 px#c4b5fdoutline offset by 14 px (10.7:1 on the background). If the card leads to a page, make it a link<a class="depth-card" href="…">. - Screen readers: the badge holds the "♦" character, read as "diamond" or similar. If it is decorative, add
aria-hidden="true"on.depth-card-icon; if the icon carries meaning (a medal, a pictogram), give it a text. - Contrast: white title at 11.4:1 on
#312e81and 11:1 on#4c1d95; text line at 70% opacity at 6.1:1 at its lowest. - Touch: with a finger, the tapped card explodes (measured with Chromium touch emulation and in Firefox) and stays open until the next tap.
Browser compatibility
3D CSS (perspective on the container, preserve-3d on the card, four planes in translateZ), :focus-visible, no dependencies, no JavaScript. Measured in Chromium 151 and Firefox 153: same exploded view (1% apart), same reduced motion, 0 errors. Playwright's WebKit flattens all preserve-3d 3D, even a reference cube: Safari rendering not checked on a real Safari.
If the browser flattens the 3D, the card turns like a flat picture: the planes no longer separate, the badge stays stuck in the corner. The text stays readable and the rotation visible.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0022-root">
<div class="depth-card-container"> <!-- perspective: 800px -->
<div class="depth-card" tabindex="0"> <!-- preserve-3d -->
<div class="depth-card-ring"></div> <!-- −20 px -->
<div class="depth-card-bg"></div> <!-- 0 -->
<div class="depth-card-content"> <!-- 30 px -->
<h3>Depth Card</h3>
<p>Hover to explore</p>
</div>
<div class="depth-card-icon">♦</div> <!-- 60 px -->
</div>
</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 |
|---|---|---|
Depths (CSS — translateZ of the ring, background, text, badge) |
−20; 0; 30; 60 px | The bigger the gaps, the clearer the exploded view, but at rest the badge grows and drifts toward the edge: around 100 px, it clearly spills over the corner. |
Hover spread (CSS — badge and ring translateZ under :hover) |
80 and −40 px | Keep the badge further forward than at rest: it is what "comes off". You can also move the text forward (add a rule for .depth-card-content). |
Angle (CSS — rotateY(-15deg) rotateX(10deg)) |
−15° and 10° | rotateY(15deg) turns the card the other way; the wider the angle, the harder the text, seen at a slant, is to read. |
Perspective (CSS — .depth-card-container) |
800 px | 500 px exaggerates the gaps and the badge's growth; 1,200 px makes them subtle. |
Duration (CSS — transition: transform .5s, three times) |
0.5 s | Change all three together (card, badge, ring), or the planes arrive at different times. |
Colors and size (CSS — .depth-card, .depth-card-bg, .depth-card-icon, .depth-card-ring) |
220 × 160 px; #312e81 → #4c1d95; badge #8b5cf6 → #a78bfa | The effect holds at 320 × 230 px as in the first scene; the badge can become round (border-radius: 50%) and carry a short text or an image. |
FAQ
translateZ and replace the fixed rotation with the one the script computes.overflow: hidden, a filter or an opacity below 1 on .depth-card make the browser paint its children flat, and the card turns like a picture. If you need to clip or blur, do it on a plane (the background, for instance), never on the card that carries preserve-3d.<img> or an SVG inside, like the wind turbine in the second scene. If the image carries meaning, give it alt text; otherwise, hide the badge from screen readers with aria-hidden="true".