CardsFree

3D Tilt Effect

Real-time two-axis 3D tilt tracking the cursor position — content floats forward and an indigo glow appears on hover.

JS3DInteractive
3D Tilt
Hover or click the scene to interact

This effect is free — the Cards category contains 41 effects total, including 6 free. Effect.Labs has 811 vanilla effects. Explore the category →

Usage examples

Popular Pro €29 / month

Hover the card to tilt it

① Pricing card grid — SaaS or e-commerce

WhenThe user compares pricing plans or browses a product card grid.
Why3D tilt on hover gives each card physical presence and signals interactivity — the dynamic indigo shadow reinforces visual hierarchy without cluttering the layout.
SettingsDivisor at 12 (moderate tilt ≈ 7°), transition: transform 0.12s, translateZ(20px) for subtle relief in a dense grid.

New release

🚀 Ship faster with Effect.Labs Interactive effects · 0 dependencies

Hover the card to tilt it

② Centered hero card — landing page or portfolio

WhenA single large card occupies the hero's central area, showcasing a strong visual, key metric, or product demo.
WhyAlone on a large surface, the card fully exploits the 1000 px perspective and the translateZ parallax — mouse interaction becomes the page's interactivity showcase.
SettingsDivisor at 8 (pronounced tilt ≈ 11°), card enlarged to 400×260 px, translateZ(50px) for heavily floating content.
👩‍💻 Sarah Chen Lead Designer UI/UX Motion

Hover the card to tilt it

③ Team member gallery — About or speakers page

WhenTeam members or event speakers are displayed in a card grid with photo, name, and role.
WhyThe tilt adds a memorable interactive touch to otherwise static content — the card lifts slightly as if coming to life under the cursor.
SettingsDivisor at 15 (gentle tilt ≈ 6°), transition: transform 0.18s, drop shadow in the brand's primary color.

How it works

On every mousemove, the script computes the cursor position relative to the card's center via getBoundingClientRect(). Two angles are derived: rotateX = (y − centerY) / 10 (forward/backward tilt) and rotateY = (centerX − x) / 10 (left/right tilt). The divisor 10 caps the tilt at roughly ±9° at the card's edge.

The transformation is applied as a single inline string: perspective(1000px) rotateX(Xdeg) rotateY(Ydeg). Using perspective() inside the transform function rather than as a parent CSS property binds the vanishing point to the element itself — each card in a grid has its own perspective without interference.

The CSS property transform-style: preserve-3d on .card-tilt propagates the 3D context to children. The .tilt-content element gets translateZ(30px), visually floating it 30 px in front of the card surface — a subtle parallax between the card background and its content.

On mouseleave, the transform resets to perspective(1000px) rotateX(0) rotateY(0) through the CSS transition: transform 0.1s — the snap-back is smooth in 100 ms. The indigo drop shadow from .card-tilt:hover follows the same native CSS mechanism without extra JS.

Accessibility

  • prefers-reduced-motion not handled: the tilt always fires on hover regardless of the system preference. Add @media (prefers-reduced-motion: reduce) { .card-tilt { transition: none; } } and disable the JS listeners if needed.
  • The effect is mouse-only (mousemove / mouseleave) — no keyboard or touch interaction. The card stays accessible and readable without interaction.
  • No ARIA attributes are added by the effect. If the card is interactive (link, button), add role, aria-label, or aria-labelledby manually.
  • Text content contrast is unaffected by the tilt: colors and brightness remain constant regardless of rotation angle.
  • The effect intercepts no keyboard events and does not trap focus — interactive child elements (links, buttons) remain normally focusable.

Browser compatibility

Requires CSS transform-style: preserve-3d and getBoundingClientRect — available in all modern browsers. Zero external dependencies.

Chrome 36+✓ Full
Firefox 16+✓ Full
Safari 9+✓ Full
Edge 79+✓ Full
Mobile iOS✓ Static (no hover)
Android Chrome✓ Static (no hover)

Without 3D CSS transform support, the card displays flat with no tilt — no JS errors, content stays readable and accessible.

The code

Copy the three blocks into your page. No dependencies.

HTML
<div class="card-demo card-tilt" id="tiltCard">
  <span class="tilt-content">3D Tilt</span>
</div>
CSS
.card-demo  {
   width: 280px;
   height: 180px;
   background: var(--bg-card);
   border-radius: var(--radius-xl);
   display: flex;
   align-items: center;
   justify-content: center;
   font-weight: 600;
   position: relative;
   cursor: pointer;
   }

.card-tilt  {
   transform-style: preserve-3d;
   transition: transform 0.1s;
   }

.card-tilt:hover  {
   box-shadow: rgba(99, 102, 241, 0.3) 0px 25px 50px;
   }

.card-tilt .tilt-content  {
   transform: translateZ(30px);
   }

/* Variables du design system du site, dont ce code depend. Sans elles,
   l'acheteur obtenait des coins carres et des couleurs par defaut (revue Owen).
   Valeurs reprises de css/style.css — a adapter librement. */
.card-demo, .card-tilt {
  --bg-card: #12121a;
  --radius-xl: 1rem;
}

/* Le fond de carte est desormais sombre : sans couleur de texte declaree,
   le libelle heritait du noir de la page hote et devenait illisible (revue Owen). */
.card-demo { color: #fff; }
JavaScript (fx-0128)
(function(){
    // 3D Tilt Effect
    const tiltCard = document.getElementById('tiltCard');
    if (tiltCard) {
      tiltCard.addEventListener('mousemove', (e) => {
        const rect = tiltCard.getBoundingClientRect();
        const x = e.clientX - rect.left;
        const y = e.clientY - rect.top;
        const centerX = rect.width / 2;
        const centerY = rect.height / 2;
        const rotateX = (y - centerY) / 10;
        const rotateY = (centerX - x) / 10;
        tiltCard.style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`;
      });
      tiltCard.addEventListener('mouseleave', () => {
        tiltCard.style.transform = 'perspective(1000px) rotateX(0) rotateY(0)';
      });
    }

    // Spotlight Effect
    const spotlightCard = document.getElementById('spotlightCard');
    if (spotlightCard) {
      spotlightCard.addEventListener('mousemove', (e) => {
        const rect = spotlightCard.getBoundingClientRect();
        const x = e.clientX - rect.left;
        const y = e.clientY - rect.top;
        spotlightCard.style.setProperty('--spotlight-x', x + 'px');
        spotlightCard.style.setProperty('--spotlight-y', y + 'px');
      });
    }

    // Card Morph Expand Effect
    const morphExpandCard = document.getElementById('morphExpandCard');
    if (morphExpandCard) {
      morphExpandCard.addEventListener('click', () => {
        morphExpandCard.classList.toggle('expanded');
      });
      // Close when clicking outside
      document.addEventListener('click', (e) => {
        if (!morphExpandCard.contains(e.target) && morphExpandCard.classList.contains('expanded')) {
          morphExpandCard.classList.remove('expanded');
        }
      });
    }
  })();

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Divisor /10 (JS — rotateX / rotateY lines) 10 Maximum tilt amplitude. Increase to soften (15 → ~6°), decrease to intensify (6 → ~15°).
perspective(1000px) (JS — transform string) 1000px Virtual camera distance. Higher = flatter perspective; lower = more pronounced 3D distortion.
transition: transform 0.1s (CSS — .card-tilt) 0.1s Cursor-follow speed. 0.05s = snappy, 0.25s = slow and languid.
translateZ(30px) (CSS — .card-tilt .tilt-content) 30px Floating depth of the content above the card surface. Increase to 60px for a holographic feel.
box-shadow (CSS — .card-tilt:hover) rgba(99,102,241,0.3) 0px 25px 50px Color and intensity of the hover drop shadow. Replace the color with your brand primary.
--bg-card (CSS variable) globally defined Card background color or gradient — redefine the variable on the card or its parent to customize.
width / height (CSS — .card-demo) 280px / 180px Card dimensions. The rotation formula uses getBoundingClientRect — resizing the card does not break the effect.

FAQ

Does the effect work on mobile (touch)?

No — the code listens to mousemove and mouseleave, which do not fire on touch. On mobile the card displays flat. To add touch support, listen to touchmove and derive rotation from e.touches[0].clientX/Y the same way.

How do I apply the tilt to multiple cards on the same page?

The provided code targets a single element by id. For multiple instances, select by class: document.querySelectorAll('.card-tilt'), then attach mousemove and mouseleave listeners to each element in a loop — each card computes its own coordinates via getBoundingClientRect().

Do child elements (buttons, links) stay clickable inside a tilted card?

Yes. The 3D transform is applied via element.style.transform — it does not intercept clicks or DOM events from children. A button or link inside the card remains fully interactive and keyboard-focusable.