Depth tilt
A card with depth. Under the pointer the card turns in perspective to face it, on a spring: a small angle, a heavy brake, nothing that wobbles.
Documentation
-
Copy to Webflow
Add Geist and Geist Mono to your site's fonts (Site settings, Fonts, Google Fonts) before pasting: Webflow removes any font your site has not installed. -
Add custom CSS
Custom CSS in WebflowThis CSS already ships inside the Webflow embed, so a pasted section needs nothing added. Paste it yourself, in the page or the site custom code, only if the effect runs on several pages. The tutorial explains where.
See the tutorialA layer does not grow as it slides: a picture that exactly fills the surface needs a little more size than its box, or an edge shows. And the default sheen is white, so a white card wants a darksheenColor. -
Add custom JS
Custom JavaScript in WebflowThis script already ships inside the Webflow embed, settings block included. You paste it yourself, before the closing body tag of the page or the site custom code, only when the effect has to run on several pages, and the tutorial says how to choose.
See the tutorial -
Use it on your own elements
How to add a custom attribute in WebflowA custom attribute is a pair, a name and a value, typed in the Settings panel of the element you selected, under Custom attributes. The name is what the script looks for, the value is what it reads. The tutorial shows the panel.
See the tutorialCustom attributesSelect the element on the canvas and open the Settings panel. Under Custom attributes, add a pair: the name on the left, the value on the right.Custom attribute What it does data-depth-tilt Marks a card: the box that keeps still, takes the pointer and carries the settings. It takes no value: leave the value field empty. Put it on any box of the page, as many as you like; each one tilts on its own. data-depth-tilt-surface On the box inside the card that turns, usually one div that fills it. It takes no value. Without it the card turns itself, and shivers when the pointer sits on its very edge. [ Optional ] data-depth-tilt-layer On anything inside the card that should float, with a number as its value: 1 slides one step of the depth setting towards the pointer, 3 three steps, and a negative number sinks the layer under the sheet so it slides the other way. Empty reads as 1. Without any layer the card turns in one piece. [ Optional ] data-depth-tilt-state Written by the engine on the card: live while it is turned or moving, idle at rest, still under reduced motion. Never written by hand. [ Optional ] SettingsEvery setting is a custom attribute too, added the same way and on the same element. An attribute always wins over the defaults written in the code.Attribute Default What it is data-depth-tilt-sheen-color #ffffff Colour of the disc of light that follows the pointer over the surface. Options: any CSS colour, white on a dark or coloured card, a dark ink on a white one; taken out of this block, --depth-tilt-sheen-color on the card takes over. data-depth-tilt-sheen 0.3 Strength of the sheen at its centre, 0 to 1. data-depth-tilt-angle 7 Tilt of the card, in degrees, when the pointer is on one of its edges. data-depth-tilt-perspective clamp(700px, 70vw, 1200px) Distance from the eye to the card, any CSS unit: how strongly the far edge shrinks as the card turns. data-depth-tilt-depth clamp(4px, 0.45vw, 8px) Distance a layer of depth 1 slides when the pointer is on an edge, any CSS unit; each layer multiplies it by its own number. data-depth-tilt-scale 1.02 Size of the card while the pointer holds it, in times its own size. data-depth-tilt-stiffness 120 Pull of the spring towards the pointer. data-depth-tilt-damping 14 Brake on that pull; with stiffness it sets how much the card overshoots.
Two files, no build step.
It runs in the browser only. Load it with a script tag in plain HTML, in a
client-side script in Astro, never in the frontmatter. In React, call
DepthTilt.mount(ref.current) in useEffect and
destroy() in its cleanup.
-
Tune it, either way
Per element with an attribute, or once for the whole site by editingDEFAULTSat the top of the file.Attribute Default What it is Lower Higher data-depth-tilt-sheen-color #ffffff Colour of the disc of light that follows the pointer over the surface. Options: any CSS colour, white on a dark or coloured card, a dark ink on a white one; taken out of this block, --depth-tilt-sheen-color on the card takes over. data-depth-tilt-sheen 0.3 Strength of the sheen at its centre, 0 to 1. At 0 no light at all, the card only turns Past 0.5 the light reads as a white spot and hides the words under it data-depth-tilt-angle 7 Tilt of the card, in degrees, when the pointer is on one of its edges. Under 4 the turn is felt more than seen Past 12 the card swings like a toy and its far edge visibly shrinks data-depth-tilt-perspective clamp(700px, 70vw, 1200px) Distance from the eye to the card, any CSS unit: how strongly the far edge shrinks as the card turns. A close eye, a strong keystone, dramatic on a large card A far eye, the card turns almost flat, without depth data-depth-tilt-depth clamp(4px, 0.45vw, 8px) Distance a layer of depth 1 slides when the pointer is on an edge, any CSS unit; each layer multiplies it by its own number. Layers that barely part, a flat card that only turns Layers that travel far, and may leave the card or cross each other data-depth-tilt-scale 1.02 Size of the card while the pointer holds it, in times its own size. At 1 the card does not grow; under 1 it sinks as if pressed Past 1.05 the card covers its neighbours data-depth-tilt-stiffness 120 Pull of the spring towards the pointer. A lazy card that trails far behind the pointer A card stuck to the pointer, nervous on a fast move data-depth-tilt-damping 14 Brake on that pull; with stiffness it sets how much the card overshoots. Under 10 the card bounces and wobbles when let go Past 22 no overshoot at all, the card creeps back to flat -
Drive it from JavaScript
When something other than the pointer should turn a card, a card posed at an angle in a hero for instance, or a card turned by a scroll position.const element = document.querySelector('[data-depth-tilt]'); const card = DepthTilt.mount(element); // the card auto() mounted, or a new one card.tilt(0.6, -0.4); // turned towards its top right, and held there card.release(); // settles back to flat // When the card leaves the page: card.destroy(); // removes the sheen and the listeners
-
Fit it to your page
- Structure: three marks, on your own elements.
data-depth-tiltgoes on the card, the box that keeps still and takes the pointer. Inside it,data-depth-tilt-surfacegoes on the box that turns. Inside the surface,data-depth-tilt-layer="2"goes on anything that should float. The preview's card is a link, its surface adivfilling the link. - Why two boxes. A box that turned under its own pointer would move its edge away from it, lose the pointer, fall back and catch it again. The card never moves, so the pointer is in or out for good; only the surface turns. Without a marked surface the card turns itself, which is fine for a box the pointer crosses and rough at its very edge.
- The number of a layer is a multiple of
depth. 1 is one step towards the eye, 3 three steps; 0 keeps the layer on the sheet; a negative number sinks it under the sheet, and it slides the other way. An empty value reads as 1. The preview uses -2 for the monogram, 0.5 for the frame, 1 for the top line, 1.5 for the weight and 3 for the name. - One setting for every card of the page, without opening the file: declare
window.DepthTiltSettings = { angle: 10 }in a script placed beforedepth-tilt.js. The three levels, least specific first, areDEFAULTS, then that object, then adata-depth-tilt-*attribute on the card itself (data-depth-tilt-angle="10"), so one card can still differ from the page-wide setting. - The colour of the sheen can also come from the stylesheet: without a
sheenColorsetting in the attributes or in the settings block, the custom property--depth-tilt-sheen-coloron the card is read, thenDEFAULTS.sheenColor. The engine writes its own copy ofDEFAULTSinto the Embed, so on a Webflow page the attributedata-depth-tilt-sheen-coloris the way to change it per card. - What the script adds: one box as the last child of the surface,
depth-tilt-sheen, with the disc of lightdepth-tilt-glowinside it. It never takes a click. - What the script writes:
data-depth-tilt-stateon the card,livefrom the moment the pointer takes it until it has settled flat again,idleat rest,stillunder reduced motion. While a card is live, an inlinetransformon its surface and an inlinetranslateon each layer; both are taken back when it rests. - **From JavaScript, beyond
mount,autoanddestroy**: the api carrieselement,surface,settings,tilt(x, y)which turns the card by hand, -1 to 1 across and down, and holds it there,release()which lets it settle, andframes()andisRunning()for a test that wants to see the loop sleep.DepthTilt.isRunning()says whether the page's one loop runs. The effect emits no event. - **
destroy()leaves the card as it was**: the sheen removed, the listeners gone, the state attribute and every inline value taken off, and the inlineposition: relativeremoved if the script set it. - Touch: a card follows the finger while it presses and settles when the finger lifts. The page still scrolls: a press that turns into a scroll is cancelled by the browser, and the card is let go.
- Keyboard: a card that takes the focus from the keyboard lifts flat, with its scale and its sheen in the middle, and settles when the focus leaves.
- **
prefers-reduced-motion**: no tilt, no slide, no scale. The sheen alone fades in at the middle of the card under the pointer and fades out when it leaves, a light change of state. The state readsstill, and turning the preference off brings the tilt back without a reload. - The words of the markup are the preview's, and they ship as they are. Put the user's own words in their place whenever they want.
- On Webflow, add the fonts first. Add Geist and Geist Mono to your site's fonts (Site settings, Fonts, Google Fonts) before pasting: the paste names them, and the Designer drops a font the site does not have.
- Structure: three marks, on your own elements.
-
Avoid the pitfalls
- The default sheen is white, and white on a white card shows nothing. On a white or very pale surface give the card a dark sheen,
data-depth-tilt-sheen-color="#15100c"with asheenaround 0.08, or a tinted surface. - A transform of your own on the surface is replaced while the card is live. The engine writes the whole
transformof the surface, and puts back the inline value it found when the card rests; a transform that comes from a class is covered for that time. Put it on a wrapper instead. The layers are moved with thetranslateproperty, which adds to a transform of their own instead of replacing it. - **
overflow: hiddenon the surface cuts the layers that slide past its edge.** That is often what is wanted for a picture; for a label near the edge, lower its number or leave the surface open. - A layer does not grow. Sliding a picture that exactly fills the surface uncovers a strip along one edge. Give it a negative
insetof at leastdepthtimes its number, or a slightly larger size, so there is picture left to slide in. - **A parent with
overflow: hiddenclips the turned card**, which is larger than its box byscaleand leans out of it. Leave a margin around the cards, or let the parent overflow. - **Settings handed to
mount()after the page loaded are dropped.**auto()mounts on DOM ready, andmount()on a card that already carries the effect returns the existing one. A page-wide block goes inwindow.DepthTiltSettings, whichauto()reads itself. - Text on a turned surface is drawn as a picture. While the card is live the browser renders the surface on its own layer; on a low density screen the words soften a little, and sharpen again at rest.
- The default sheen is white, and white on a white card shows nothing. On a white or very pale surface give the card a dark sheen,
One click on Copy the AI prompt copies everything an assistant needs to build this effect: the complete code, the markup, the dependency, every setting and every pitfall.
What it does
Depth tilt is a cursor animation for Webflow and vanilla JavaScript, with no library. Also called parallax tilt card, 3D tilt card or 3D card hover effect.
A card with depth. Under the pointer the card turns in perspective to face it, on a spring: a small angle, a heavy brake, nothing that wobbles. Any element inside marked with a depth and a number slides by its own amount, so the picture, the title and the label come apart as if they sat at different heights over the sheet, and a layer with a negative number sinks under it and slides the other way. A soft disc of light follows the pointer across the surface. On leave everything goes back to flat with a little overshoot, and the inline values the tilt wrote are taken away, so a card at rest is the stylesheet's own. Any number of cards on a page share one loop, which only runs while a card moves. On touch a card follows the finger while it presses and settles when it lifts. No picture to load, no canvas, no library: transforms on the page's own elements.
Updated
Questions
Add three custom attributes to your own elements: data-depth-tilt on the card, which keeps still and takes the pointer, data-depth-tilt-surface on the box inside it that turns, and data-depth-tilt-layer="2" on anything inside the surface that should float. Settings go on the card, for instance data-depth-tilt-angle="10".
No. There is no library, no canvas and no picture to load: the engine writes CSS 3D transforms on the page's own elements and moves them on a damped spring. Any number of cards on a page share one loop, which only runs while a card moves.
Set data-depth-tilt-angle, the tilt of the card in degrees when the pointer is on one of its edges, 7 by default. Under 4 the turn is felt more than seen; past 12 the card swings like a toy and its far edge visibly shrinks. data-depth-tilt-perspective sets how strongly that far edge shrinks.
Each element marked data-depth-tilt-layer slides by depth, clamp(4px, 0.45vw, 8px) by default, times its own number. 1 is one step towards the eye, 0 keeps the layer on the sheet, and a negative number sinks it under the sheet so it slides the other way. A layer does not grow, so a picture that exactly fills the surface needs a negative inset to have picture left to slide in.
Yes. On touch a card follows the finger while it presses and settles when the finger lifts, and the page still scrolls: a press that turns into a scroll is cancelled by the browser. Under prefers-reduced-motion there is no tilt, no slide and no scale, and the sheen alone fades in at the middle of the card.