Pure CSS True 3D Button
Discover how to make a chunky, glowing, 3D button that animates on hover and click, with no JavaScript! Just clever use of HTML and 3D CSS transforms.
Sometimes you just need a big, shiny call-to-action that draws the user’s attention to the most important thing on a page, but you’ve already exhausted your UI framework’s colors, borders and button options. You want this thing to really pop.
…and what could pop more than a chunky, glowing, animated 3D button? 😎
Feels rewarding to click, doesn’t it?
The CSS and HTML markup are provided below. The key parts to note are:
- CSS variables allowing reuse of repetitive properties (e.g. color)
- 3D transforms to move and rotate the bottom and side faces of the button into realistic positions
perspective
andperspective-origin
on the whole button to reveal the bottom and side facesfilter: brightness
to darken the bottom and side faces and light up the front face on hoverbox-shadow
for the glow effecttransform: scale
andtransform-origin
1 to change the sizes of the bottom and side faces on hover/clicktransform: translateZ
to move the front face in and out of the “screen” on hover/click
.chunky-button { border: none; background: none; padding: 0; border-radius: 0; position: relative; perspective: 2000px; perspective-origin: 1200px 1000px; transform-style: preserve-3d; cursor: default; font-size: 0.8rem; user-select: none; --color: #46e4bc; --transition: transform 0.1s ease, box-shadow 0.1s ease; --virtual-height: 10px; --factor: 0.38; } .chunky-button .front { background-color: var(--color); color: rgba(0, 0, 0, 0.7); font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; padding: 1rem 2rem; transition: var(--transition); border: 1px solid var(--color); backface-visibility: hidden; } .chunky-button:hover .front { transform: translateZ(calc(var(--virtual-height)*var(--factor))); box-shadow: 0 0 3rem var(--color); filter: brightness(1.1); } .chunky-button:active .front { transform: translateZ(calc(-1*var(--virtual-height)*var(--factor))); } .chunky-button .bottom { background-color: var(--color); filter: brightness(0.8); height: var(--virtual-height); width: 100%; position: absolute; left: 0; bottom: 0; transform-origin: bottom; --initial-position: rotateX(-90deg) translateY(100%); transform: var(--initial-position); transition: var(--transition); } .chunky-button:hover .bottom { transform: var(--initial-position) scaleY(calc(1 + var(--factor))); } .chunky-button:active .bottom { transform: var(--initial-position) scaleY(calc(1 - var(--factor))); } .chunky-button .side { background-color: var(--color); filter: brightness(0.9); width: var(--virtual-height); height: 100%; position: absolute; top: 0; right: 0; transform-origin: right; --initial-position: rotateY(90deg) translateX(100%); transform: var(--initial-position); transition: var(--transition); } .chunky-button:hover .side { transform: var(--initial-position) scaleX(calc(1 + var(--factor))); } .chunky-button:active .side { transform: var(--initial-position) scaleX(calc(1 - var(--factor))); }
Then place this HTML where you want the marker to appear.
<div class="chunky-button"> <div class="bottom"></div> <div class="side"></div> <div class="front"> Click me, I won't hurt you! </div> </div>
Footnotes:
I went through many iterations of janky and crunchy animations before figuring out that transform-origin
was the best approach to anchoring the bottom and side faces of the button “in place” while they changed sizes. It’s important to use the simplest transform
changes possible on hover/click to keep the animation clean.