3D Button Press
A real violet button sitting on a darker 8 px solid edge, like a key: it rises 2 px on hover (10 px edge) and sinks 6 px when pressed (edge down to 2 px, tighter shadow), in 0.15 s — the bottom of the edge never moves, the key sinks into its base. CSS only, no JavaScript, motionless when the user reduces motion.
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-0006-root (#0a0a0f background, at least 300 px tall, --radius-lg variable defined on it) centers a 280 × 200 px .demo-3d frame, then .button-3d-container, which carries one thing only: perspective: 500px. Inside sits a real <button class="button-3d">: 16 px × 40 px padding, bold 16 px white text, vertical gradient #6366f1 → #4f46e5, 0.75 rem corners. The edge is not an element: it is a shadow with no blur, box-shadow: #3730a3 0 8px 0, offset 8 px down, which draws a solid base under the button; a second, blurred shadow (rgba(0, 0, 0, .4) 0 15px 20px) sets it on the background.
Three states, one 0.15 s transition on everything. At rest, translateZ(20px) inside the 500 px perspective shows the button at 104% (156 × 52 px measured). On hover it rises 2 px (translateY(-2px)) and the edge grows to 10 px. When pressed (:active) it drops 6 px and moves back to translateZ(10px), the edge shrinks to 2 px and the blurred shadow tightens (5 px / 10 px). Do the math: at rest, the bottom of the edge is 0 + 8 = 8 px below the button; on hover −2 + 10 = 8 px; pressed 6 + 2 = 8 px. The base stays put while the key rises and then sinks into it. Measured in Chromium 151: 1.1% of pixels change on hover, 2.9% when pressed; Firefox 153: 0.9% and 2.7%.
Why a shadow rather than a pseudo-element or a border: it follows the button's corners, takes no room in the layout (neighbors don't move when the edge changes thickness) and transitions with everything else. The perspective adds a nuance: the button looks slightly closer at rest (×1.04) than pressed (×1.02). On the keyboard it is an ordinary button: Tab, Enter, Space, and a 3 px #a5b4fc :focus-visible ring offset by 4 px. In Chromium, holding Space presses the key like the mouse does (3.9% of pixels changed); Firefox does not apply :active from the keyboard (0% measured): the click still fires, the key just doesn't sink.
Accessibility
- Reduced motion: under
prefers-reduced-motion: reduce, a block scoped to.fx-0006-rootkeepstranslateZ(20px)on hover and press: the key neither rises nor sinks. The edge thickness (8, 10, then 2 px) and the shadow still mark both states. Measured in Chromium and Firefox: same transform at rest, on hover and pressed; 0.6% of pixels change on hover and 1.2% when pressed (versus 1.1% and 2.9% without the preference). - Keyboard: a real
<button>, reached with Tab and triggered by Enter or Space. The#a5b4fcfocus ring measures 9.9:1 on the#0a0a0fbackground. In Safari, Tab only stops on buttons when the "Press Tab to highlight each item" option is on (otherwise Option + Tab): that is the browser's setting, measured in WebKit, not a flaw of the effect. - Contrast: bold 16 px white text at 4.47:1 at the top of the gradient (
#6366f1) and 6.3:1 at the bottom (#4f46e5). The top is just under the 4.5:1 threshold: for text that matters, darken the top color (#5b5bd6gives 5.4:1). - Touch: with a finger, the browser gives the button its hover state (measured with Chromium touch emulation and in Firefox); the press follows
:active, for as long as the finger is down. Not checked on a real iPhone, where Safari shows:activeonly under some conditions.
Browser compatibility
CSS only (box-shadow, transform inside a perspective parent, :focus-visible), no dependencies. Measured in Chromium 151, Firefox 153 and WebKit 26.5: same render at rest, on hover and pressed (1% differences from text smoothing), same stillness under reduced motion, 0 errors. The button does not stack 3D planes: Playwright's WebKit, which flattens preserve-3d scenes, renders it like Chromium.
Without :focus-visible (old browser), the browser's default focus ring shows instead of the violet outline. Without transitions, the three states switch at once: the key still sinks.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0006-root">
<div class="demo-3d">
<div class="button-3d-container"> <!-- perspective: 500px -->
<button class="button-3d">Press Me</button>
</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 |
|---|---|---|
Key color (CSS — background of .button-3d) |
#6366f1 → #4f46e5 | Vertical gradient, light at the top: it mimics light falling on the top of the key. Keep the edge clearly darker than the bottom of the gradient, or the base blends into the button. |
Edge color (CSS — first color of the three box-shadow) |
#3730a3 | Change it in all three rules (rest, :hover, :active), or the edge changes color when pressed. |
Edge thickness and travel (CSS — 8px, 10px, 2px and translateY) |
8 px at rest; −2 px / 10 px on hover; +6 px / 2 px pressed | Keep offset + edge equal in all three states (here 8 px): that is what keeps the bottom of the base still. For a thicker key: 12 px at rest, −2 / 14 on hover, +9 / 3 pressed. |
Speed (CSS — transition: .15s) |
0.15 s | Under 0.1 s the press feels dry; above 0.25 s the key feels mushy. The transition covers everything: color, shadow and position move together. |
Depth (CSS — perspective: 500px and translateZ) |
500 px; 20 px at rest, 10 px pressed | Moving back from 20 to 10 px makes the key slightly smaller when pressed (×1.04 → ×1.02). Remove translateZ from all three states for a perfectly flat button: the vertical travel is enough. |
Size (CSS — padding and font-size) |
16 px × 40 px; 16 px | The relief holds at any size: the scenes enlarge it to 22 px × 46 px and 26 px without touching anything else. |
FAQ
#8b5cf6 → #6366f1 gradient, 10 px edge) and adds a 2° tilt on hover and 5° when pressed, almost invisible: 1.5% of pixels change when pressed versus 2.9% here. This one keeps the bottom of the edge still, which gives the clearest feel of a key sinking into its base.<button>, Tab reaches it, Enter and Space trigger it, and a violet ring shows the focus. The visible press follows :active: from the keyboard, Chromium shows it while Space is held, Firefox does not. With a finger, Chromium and Firefox apply the hover state on touch; on iPhone it has not been checked.