3D tilt card hover effect
A card that turns to face your pointer as it moves, with a glare that follows. Move your mouse over it to try.
UI loop
Cards that tilt toward you
Issue 04
Cards that tilt toward you
Sundry Millsundrymill.com/library
Everything in the preview is live. Try it.
The code
Tilt card
The card turns to face your pointer and a soft glare follows it.
HTML
<div class="tilt" data-tilt>
<div class="tilt-card">
<span class="tilt-kicker">Issue 04</span>
<strong class="tilt-title">Cards that tilt toward you</strong>
<span class="tilt-glare" aria-hidden="true"></span>
</div>
</div>
CSS
.tilt { width: min(100%, 260px); perspective: 900px; }
.tilt-card {
position: relative;
aspect-ratio: 3 / 4;
display: flex;
flex-direction: column;
justify-content: flex-end;
gap: 6px;
padding: 22px;
border-radius: 20px;
overflow: hidden;
color: #FFFFFF;
background:
radial-gradient(120% 90% at 15% 5%, #FFB23F 0%, transparent 55%),
radial-gradient(100% 80% at 95% 100%, #2F55D4 0%, transparent 60%),
linear-gradient(160deg, #FF7A2F, #7A3450);
box-shadow: 0 30px 50px -30px rgba(23, 32, 44, .7);
transform: rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));
transition: transform .5s cubic-bezier(.05, .7, .1, 1);
}
.tilt.is-moving .tilt-card { transition: transform .08s linear; }
.tilt-kicker { font-size: .8rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; }
.tilt-title { font-size: 1.5rem; line-height: 1.1; }
.tilt-glare {
position: absolute;
inset: 0;
background: radial-gradient(circle at var(--gx, 50%) var(--gy, 50%), rgba(255, 255, 255, .45), transparent 55%);
opacity: var(--go, 0);
transition: opacity .3s;
pointer-events: none;
}
JavaScript
const TILT = 12; // degrees at the edge
document.querySelectorAll('[data-tilt]').forEach((wrap) => {
const card = wrap.firstElementChild;
if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;
wrap.addEventListener('pointermove', (e) => {
const r = card.getBoundingClientRect();
const x = (e.clientX - r.left) / r.width;
const y = (e.clientY - r.top) / r.height;
wrap.classList.add('is-moving');
card.style.setProperty('--ry', ((x - 0.5) * 2 * TILT).toFixed(2) + 'deg');
card.style.setProperty('--rx', ((0.5 - y) * 2 * TILT).toFixed(2) + 'deg');
card.style.setProperty('--gx', (x * 100).toFixed(1) + '%');
card.style.setProperty('--gy', (y * 100).toFixed(1) + '%');
card.style.setProperty('--go', 1);
});
wrap.addEventListener('pointerleave', () => {
wrap.classList.remove('is-moving');
['--rx', '--ry', '--go'].forEach((p) => card.style.removeProperty(p));
});
});
How to use it
- Keep the tilt small. Around 12 degrees at the edge feels solid; 30 feels like the card is falling over.
- The script does nothing when someone has asked for reduced motion, and touch screens only tilt while a finger is on the card.
- Use it for one or two feature cards, not a grid of forty.
Showing an interaction like this to a client or in a tutorial? Framok records your screen and zooms in on every click for you.
Free to use in personal and commercial work. No credit needed, though a link back is always nice.