3D Button
2025.05.14#showcaseClick on the button below to witness a very neat effect that makes the button feel like it goes into the page. Let's breakdown how it works:
HTML setup
One div needed, we will use the ::before and ::after pseudo-elements to create the 'sides' of the button
<button class="three-d">3D button</button>CSS <button> styling
The .three-d class applied to <button/> is defined as follows:
.three-d {
--size: 10px;
--negative-size: calc(-1 * var(--size));
--half-size: calc(var(--size) / 2);
--negative-half-size: calc(-1 * var(--half-size));
--quarter-size: calc(var(--size) / 4);
--deg: 45deg;
--teal: hsla(180, 40%, 50%, 1);
--plum: hsla(320, 45%, 45%, 1);
position: relative;
margin-top: 1rem;
color: white;
padding: 1rem 2rem;
background: linear-gradient(90deg, var(--teal) 0, var(--plum) 100%);
font-size: 2rem;
font-family: "Arial";
border: none;
}--sizesets up a base value we can adjust later to programmatically modify downstream attributes using the value that help to keep our button looking 3D.--negative-size,--half-size,--negative-half-size,--quarter-sizeare pre calculated values to evenly adjust the overall button in attributes used on the pseudo elements.--deg, will be for skewing the bottom and side elements of the 3D button.--teal,--plumare colors that have good contrast on black and white backgrounds we will use.position:relativeis important to control where the::before,::afterelements will ultimately be placed.background: linear-gradient(...)needs to start with the90degvalue so the gradient goes from left to right (default is top to bottom). Then we transition from the colortealtoplum.
Bottom side element setup
This sets up the bottom part of button for the 3D effect:
.three-d::before {
left: var(--half-size);
bottom: var(--negative-size);
width: 100%;
height: var(--size);
background-color: var(--teal);
transform: skewX(var(--deg));
}Let's look at some of these calcuations.
transform: skewX(...)creates the correct shape for a 3D effect on the bottom of the button. Skewing on the x-axis will change the left and right sides of the::beforeelement.leftcalculation pushes the element to the right just enough so the top-left skewed corner touches the left side of the<button/>element.bottomwould normally start the::beforeelement inside the<button/>element, but the calculation resulting in a negative value shifts it down into place exactly at the bottom-left corner of the<button/>element.
On the left below is the skewed shape in isolation and on the right the borders of the <button/> element have been dotted in.
Right side element setup
.three-d::after {
top: var(--half-size);
right: var(--negative-size);
width: var(--size);
height: 100%;
background-color: var(--plum);
transform: skewY(var(--deg));
}Similar calculations happen here but oriented to the right element. Below we have on the left the ::after element in isolation and on the right borders dotted in.
Enabling the animation with transition
.three-d,
.three-d:before,
.three-d:after {
transition: all 0.2s;
}No complex keyframe animations needed here since we are doing a simple not-pressed -> pressed state change on the button which is perfect for transition. The 3 elements involved will work in unison and use all attributes to simplify declarations needing to made for each animated piece. Animation duration is 0.2s and by default our timing function is ease.
When you actually click
To make this interactive we need to display motion during the ::active pseudo-class state (a.k.a when the button is actively pressed) among all the elements <button/>, ::after, ::before
.three-d:active {
transform: translate(var(--half-size), var(--half-size));
}
.three-d:active:before {
left: var(--quarter-size);
bottom: var(--negative-half-size);
height: var(--half-size);
}
.three-d:active:after {
top: var(--quarter-size);
right: var(--negative-half-size);
width: var(--half-size);
}Variable usage comes in to play heavily here to take out a lot of the potential calc statement and keep things "DRY". These changes are what give the feeling that the button is being 'pressed' into the page. Play around with initial --size variable to see the effect of things on the full demo on codpen.
~ Zano