Buttons & forms
Push button
Pure CSSPress it. The edge is a generated box-shadow stack that collapses on :active.
Pointer-following tilt without JavaScript: nine invisible hover zones steer the card.
Your edited version
zone:hover ~ .card can set a tilt toward that zone.pointer-events: none on the card lets the pointer fall through to the zones. More zones = smoother tilt; the JS version is smoother still.3 × 3 grid of hover zones:hover ~ sibling selectorpointer-events: none on the card<div class="zones">
<i></i><i></i><i></i>
<i></i><i></i><i></i>
<i></i><i></i><i></i>
<div class="card">Hover around me</div>
</div>
.zones {
position: relative;
display: grid;
grid-template: repeat(3, 1fr) / repeat(3, 1fr);
width: 420px;
height: 300px;
perspective: 800px;
}
.zones i { z-index: 1; } /* invisible hover targets, above the card */
.card {
position: absolute;
inset: 0;
display: grid;
place-items: center;
width: 260px;
height: 160px;
margin: auto;
border-radius: 18px;
color: #fff;
font: 800 1.1rem system-ui;
background: linear-gradient(135deg, #2ee6d6, #8b6cff);
box-shadow: 0 22px 36px -16px #8b6cff;
pointer-events: none;
transition: transform 0.35s ease-out;
}
/* row 1 tilts back, row 3 tilts forward; column 1 turns left, column 3 turns right */
.zones i:nth-child(1):hover ~ .card { transform: rotateX(22deg) rotateY(-22deg); }
.zones i:nth-child(2):hover ~ .card { transform: rotateX(22deg); }
.zones i:nth-child(3):hover ~ .card { transform: rotateX(22deg) rotateY(22deg); }
.zones i:nth-child(4):hover ~ .card { transform: rotateY(-22deg); }
.zones i:nth-child(6):hover ~ .card { transform: rotateY(22deg); }
.zones i:nth-child(7):hover ~ .card { transform: rotateX(-22deg) rotateY(-22deg); }
.zones i:nth-child(8):hover ~ .card { transform: rotateX(-22deg); }
.zones i:nth-child(9):hover ~ .card { transform: rotateX(-22deg) rotateY(22deg); }
$tilt: 22deg;
.d-zones {
position: absolute;
inset: 0;
display: grid;
grid-template: repeat(3, 1fr) / repeat(3, 1fr);
transform-style: preserve-3d;
// The container lies on exactly the same 3D plane as its children. Coplanar surfaces have no
// stable front-to-back order, so in patches the browser hit-tests the (invisible) container
// instead of the child: wrong cursor, ignored hover. Only the children should catch the pointer.
pointer-events: none;
// nine invisible hover targets covering the stage
i {
z-index: 1;
pointer-events: auto;
}
&__card {
position: absolute;
inset: 0;
display: grid;
place-content: center;
gap: 2px;
width: 200px;
height: 124px;
margin: auto;
border-radius: 16px;
background: linear-gradient(135deg, var(--accent-2), var(--accent));
color: #fff;
font-weight: 800;
text-align: center;
box-shadow: 0 22px 36px -16px var(--accent);
pointer-events: none; // let the pointer reach the zones underneath
transition: transform 0.35s ease-out;
small {
font-weight: 500;
opacity: 0.85;
}
}
// zone (row, col) tilts the card toward that corner
@for $row from 0 through 2 {
@for $col from 0 through 2 {
i:nth-child(#{$row * 3 + $col + 1}):hover ~ &__card {
transform: rotateX((1 - $row) * $tilt) rotateY(($col - 1) * $tilt);
}
}
}
}