Buttons & forms
Rolling button
Pure CSSThe button is a bar with two faces; hover rolls the second one into view.
Press it. The edge is a generated box-shadow stack that collapses on :active.
Your edited version
box-shadows going straight down.<button> is a static, invisible hit target; the visible cap is a <span> inside it. A pressed element that moves itself can slide out from under the pointer.:active, move the cap down with translateY and shrink the stack by the same amount — the base appears to stay put.rotateX tilt sells the perspective.box-shadow stack:active on a static hit targetrotateX tilt<div class="scene">
<button class="push" type="button"><span>PUSH</span></button>
</div>
.scene {
perspective: 600px;
}
/* static hit target (its bottom padding covers where the cap travels to) */
.push {
padding: 0 0 8px;
border: 0;
background: none;
cursor: pointer;
transform: rotateX(30deg);
}
/* the visible cap */
.push span {
display: block;
padding: 18px 46px;
border-radius: 16px;
font: 900 1.4rem system-ui;
letter-spacing: 0.14em;
color: #fff;
background: linear-gradient(#ff4d9d, #d63a80);
pointer-events: none;
box-shadow:
0 1px 0 #8f2a58, 0 2px 0 #8f2a58, 0 3px 0 #8f2a58,
0 4px 0 #8f2a58, 0 5px 0 #8f2a58, 0 6px 0 #8f2a58,
0 7px 0 #8f2a58, 0 8px 0 #8f2a58, 0 9px 0 #8f2a58,
0 10px 0 #8f2a58,
0 18px 22px rgb(0 0 0 / 0.55);
transition: transform 0.08s, box-shadow 0.08s;
}
.push:active span {
transform: translateY(8px);
box-shadow:
0 1px 0 #8f2a58, 0 2px 0 #8f2a58,
0 6px 10px rgb(0 0 0 / 0.55);
}
@use 'sass:list';
// A solid edge made from $depth stacked 1px shadows, plus one soft drop shadow.
@function edge($depth, $color) {
$shadows: ();
@for $i from 1 through $depth {
$shadows: list.append($shadows, 0 #{$i}px 0 $color, comma);
}
@return list.append($shadows, 0 #{$depth + 8}px 22px rgb(0 0 0 / 0.55), comma);
}
$edge: color-mix(in srgb, var(--hot) 55%, #000);
$travel: 9px;
// The <button> is a static, invisible hit target; the visible cap is the <span> inside it.
// A pressed element that moves itself can slide out from under the pointer.
.d-button {
padding: 0 0 $travel; // the hit area also covers where the cap travels to
border: 0;
background: none;
cursor: pointer;
transform-style: preserve-3d;
transform: rotateX(32deg);
span {
display: block;
padding: 18px 44px;
border-radius: 16px;
background: linear-gradient(var(--hot), color-mix(in srgb, var(--hot) 80%, #000));
color: #fff;
font-size: 22px;
font-weight: 900;
letter-spacing: 0.14em;
pointer-events: none;
box-shadow: edge(12, $edge);
transition:
transform 0.08s,
box-shadow 0.08s,
filter 0.2s;
}
&:hover span {
filter: brightness(1.1);
}
&:active span {
transform: translateY($travel);
box-shadow: edge(3, $edge);
}
}