Button with Slanted Reveal
A CSS-only button hover effect where a slanted background sweeps in from below and duplicate label text slides in on a diagonal path using CSS custom properties for rotation and movement. Supports both hover and focus-visible states.
Code
<a href="#" class="btn-slanted">
<div class="btn-slanted-label__wrap">
<span class="btn-slanted-label">Slanted Button</span>
<span aria-hidden="true" class="btn-slanted-label">Slanted Button</span>
</div>
<div class="btn-slanted-bg"></div>
</a>.btn-slanted {
color: #fff;
background-color: #146ef5;
border-radius: .125em;
padding: .625em 1em;
text-decoration: none;
position: relative;
overflow: hidden;
--rotate: 20deg;
--move: 150%;
}
.btn-slanted-label__wrap {
z-index: 1;
place-items: center;
display: grid;
position: relative;
}
.btn-slanted-bg {
z-index: 0;
transform-origin: 50% 0;
border-radius: inherit;
background-color: #002f79;
width: 120%;
height: 100%;
position: absolute;
top: auto;
bottom: 0%;
right: 0;
transform: rotate(-30deg)translate(0%, 200%);
transition: transform 0.45s cubic-bezier(0.625, 0.05, 0, 1);
}
.btn-slanted-label {
transform-origin: 50% 1000%;
transition: transform 0.45s cubic-bezier(0.625, 0.05, 0, 1);
}
.btn-slanted-label:not(:nth-of-type(1)) {
position: absolute;
}
.btn-slanted-label:nth-of-type(1) {
transform: translate(calc(var(--move) * 0), calc(var(--move) * 0)) rotate(calc(var(--rotate) * 0));
transition-delay: 0.075s;
}
.btn-slanted-label:nth-of-type(2) {
transform: translate(calc(var(--move) * -0.2), calc(var(--move) * 2.5)) rotate(calc(var(--rotate) * -2));
transition-delay: 0s;
}
/* Hover styles */
@media (hover: hover) {
.btn-slanted:hover .btn-slanted-label:nth-of-type(1) {
transform: translate(calc(var(--move) * 0.2), calc(var(--move) * -2.5)) rotate(calc(var(--rotate) * 2));
transition-delay: 0s;
}
.btn-slanted:hover .btn-slanted-label:nth-of-type(2) {
transform: translate(calc(var(--move) * 0), calc(var(--move) * 0)) rotate(calc(var(--rotate) * 0));
transition-delay: 0.075s;
}
.btn-slanted:hover .btn-slanted-bg {
transform: translate(0%, 0%) rotate(0deg);
}
}
/* Focus styles */
.btn-slanted:focus-visible .btn-slanted-label:nth-of-type(1) {
transform: translate(calc(var(--move) * 0.2), calc(var(--move) * -2.5)) rotate(calc(var(--rotate) * 2));
transition-delay: 0s;
}
.btn-slanted:focus-visible .btn-slanted-label:nth-of-type(2) {
transform: translate(calc(var(--move) * 0), calc(var(--move) * 0)) rotate(calc(var(--rotate) * 0));
transition-delay: 0.075s;
}
.btn-slanted:focus-visible .btn-slanted-bg {
transform: translate(0%, 0%) rotate(0deg);
}Guide
Overview
The button stacks two identical label spans using CSS grid's place-items: center. On hover the first span exits on a diagonal path while the second enters from below. A rotated background div sweeps up simultaneously.
CSS Variables
--rotate controls the rotation multiplier and --move controls the X/Y distance multiplier. Both are set on .btn-slanted and referenced in the transform calculations. To share values across all buttons, move them to :root instead.
Tweaking Values
Temporarily set overflow: visible on .btn-slanted while adjusting --rotate and --move so you can see exactly how the labels travel outside the button bounds before clipping them.
Accessibility
The second label span has aria-hidden="true" so screen readers only announce the text once. The hover styles are also applied on :focus-visible so keyboard users get the same effect.