20 CSS Animated Buttons10 / 20
CSS Text Flip Button Hover
Button labels flip vertically on hover to reveal a secondary message — built with CSS 3D perspective transforms on stacked spans, no JavaScript required.
Published
The code
<div class="ab-10">
<p class="ab-10__label">Hover to flip text</p>
<div class="ab-10__row">
<button class="ab-10__btn ab-10__btn--indigo">
<span class="ab-10__inner">
<span class="ab-10__front">View Pricing</span>
<span class="ab-10__back">From $9/mo →</span>
</span>
</button>
<button class="ab-10__btn ab-10__btn--teal">
<span class="ab-10__inner">
<span class="ab-10__front">Follow Us</span>
<span class="ab-10__back">@codefronts</span>
</span>
</button>
<button class="ab-10__btn ab-10__btn--rose">
<span class="ab-10__inner">
<span class="ab-10__front">Join Beta</span>
<span class="ab-10__back">Spots Left: 3</span>
</span>
</button>
</div>
</div><div class="ab-10">
<p class="ab-10__label">Hover to flip text</p>
<div class="ab-10__row">
<button class="ab-10__btn ab-10__btn--indigo">
<span class="ab-10__inner">
<span class="ab-10__front">View Pricing</span>
<span class="ab-10__back">From $9/mo →</span>
</span>
</button>
<button class="ab-10__btn ab-10__btn--teal">
<span class="ab-10__inner">
<span class="ab-10__front">Follow Us</span>
<span class="ab-10__back">@codefronts</span>
</span>
</button>
<button class="ab-10__btn ab-10__btn--rose">
<span class="ab-10__inner">
<span class="ab-10__front">Join Beta</span>
<span class="ab-10__back">Spots Left: 3</span>
</span>
</button>
</div>
</div>.ab-10,
.ab-10 *,
.ab-10 *::before,
.ab-10 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.ab-10 ::selection {
background: #4f46e5;
color: #fff;
}
.ab-10 {
font-family: system-ui,sans-serif;
background: #0f0c1f;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2rem;
padding: 2rem;
}
.ab-10__label {
font-size: .78rem;
letter-spacing: .14em;
text-transform: uppercase;
color: #818cf8;
opacity: .6;
}
.ab-10__row {
display: flex;
gap: 1.25rem;
flex-wrap: wrap;
justify-content: center;
}
.ab-10__btn {
--c: #4f46e5;
padding: 0;
background: var(--c);
border: none;
border-radius: 10px;
cursor: pointer;
outline: none;
perspective: 600px;
}
.ab-10__btn--indigo {
--c: #4f46e5;
}
.ab-10__btn--teal {
--c: #0d9488;
}
.ab-10__btn--rose {
--c: #e11d48;
}
.ab-10__inner {
position: relative;
display: block;
padding: .85rem 2rem;
width: 9rem;
height: 3.1rem;
overflow: hidden;
transform-style: preserve-3d;
}
.ab-10__front,
.ab-10__back {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: .95rem;
font-weight: 700;
color: #fff;
letter-spacing: .04em;
white-space: nowrap;
backface-visibility: hidden;
transition: transform .35s cubic-bezier(.4,0,.2,1);
}
.ab-10__front {
transform: translateY(0) rotateX(0);
}
.ab-10__back {
transform: translateY(100%) rotateX(-90deg);
}
.ab-10__btn:hover .ab-10__front {
transform: translateY(-100%) rotateX(90deg);
}
.ab-10__btn:hover .ab-10__back {
transform: translateY(0) rotateX(0);
}
@media (prefers-reduced-motion: reduce) {
.ab-10__front,
.ab-10__back {
transition: none;
}
.ab-10__btn:hover .ab-10__front {
transform: none;
opacity: 0;
}
.ab-10__btn:hover .ab-10__back {
transform: none;
opacity: 1;
}
}.ab-10,
.ab-10 *,
.ab-10 *::before,
.ab-10 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.ab-10 ::selection {
background: #4f46e5;
color: #fff;
}
.ab-10 {
font-family: system-ui,sans-serif;
background: #0f0c1f;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2rem;
padding: 2rem;
}
.ab-10__label {
font-size: .78rem;
letter-spacing: .14em;
text-transform: uppercase;
color: #818cf8;
opacity: .6;
}
.ab-10__row {
display: flex;
gap: 1.25rem;
flex-wrap: wrap;
justify-content: center;
}
.ab-10__btn {
--c: #4f46e5;
padding: 0;
background: var(--c);
border: none;
border-radius: 10px;
cursor: pointer;
outline: none;
perspective: 600px;
}
.ab-10__btn--indigo {
--c: #4f46e5;
}
.ab-10__btn--teal {
--c: #0d9488;
}
.ab-10__btn--rose {
--c: #e11d48;
}
.ab-10__inner {
position: relative;
display: block;
padding: .85rem 2rem;
width: 9rem;
height: 3.1rem;
overflow: hidden;
transform-style: preserve-3d;
}
.ab-10__front,
.ab-10__back {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: .95rem;
font-weight: 700;
color: #fff;
letter-spacing: .04em;
white-space: nowrap;
backface-visibility: hidden;
transition: transform .35s cubic-bezier(.4,0,.2,1);
}
.ab-10__front {
transform: translateY(0) rotateX(0);
}
.ab-10__back {
transform: translateY(100%) rotateX(-90deg);
}
.ab-10__btn:hover .ab-10__front {
transform: translateY(-100%) rotateX(90deg);
}
.ab-10__btn:hover .ab-10__back {
transform: translateY(0) rotateX(0);
}
@media (prefers-reduced-motion: reduce) {
.ab-10__front,
.ab-10__back {
transition: none;
}
.ab-10__btn:hover .ab-10__front {
transform: none;
opacity: 0;
}
.ab-10__btn:hover .ab-10__back {
transform: none;
opacity: 1;
}
}Here's a working CSS Animated Button from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: CSS Text Flip Button Hover
Source: https://codefronts.com/motion/css-animated-buttons/css-text-flip-button-hover/
Button labels flip vertically on hover to reveal a secondary message — built with CSS 3D perspective transforms on stacked spans, no JavaScript required.
## HTML
```html
<div class="ab-10">
<p class="ab-10__label">Hover to flip text</p>
<div class="ab-10__row">
<button class="ab-10__btn ab-10__btn--indigo">
<span class="ab-10__inner">
<span class="ab-10__front">View Pricing</span>
<span class="ab-10__back">From $9/mo →</span>
</span>
</button>
<button class="ab-10__btn ab-10__btn--teal">
<span class="ab-10__inner">
<span class="ab-10__front">Follow Us</span>
<span class="ab-10__back">@codefronts</span>
</span>
</button>
<button class="ab-10__btn ab-10__btn--rose">
<span class="ab-10__inner">
<span class="ab-10__front">Join Beta</span>
<span class="ab-10__back">Spots Left: 3</span>
</span>
</button>
</div>
</div>
```
## CSS
```css
.ab-10,
.ab-10 *,
.ab-10 *::before,
.ab-10 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.ab-10 ::selection {
background: #4f46e5;
color: #fff;
}
.ab-10 {
font-family: system-ui,sans-serif;
background: #0f0c1f;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2rem;
padding: 2rem;
}
.ab-10__label {
font-size: .78rem;
letter-spacing: .14em;
text-transform: uppercase;
color: #818cf8;
opacity: .6;
}
.ab-10__row {
display: flex;
gap: 1.25rem;
flex-wrap: wrap;
justify-content: center;
}
.ab-10__btn {
--c: #4f46e5;
padding: 0;
background: var(--c);
border: none;
border-radius: 10px;
cursor: pointer;
outline: none;
perspective: 600px;
}
.ab-10__btn--indigo {
--c: #4f46e5;
}
.ab-10__btn--teal {
--c: #0d9488;
}
.ab-10__btn--rose {
--c: #e11d48;
}
.ab-10__inner {
position: relative;
display: block;
padding: .85rem 2rem;
width: 9rem;
height: 3.1rem;
overflow: hidden;
transform-style: preserve-3d;
}
.ab-10__front,
.ab-10__back {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: .95rem;
font-weight: 700;
color: #fff;
letter-spacing: .04em;
white-space: nowrap;
backface-visibility: hidden;
transition: transform .35s cubic-bezier(.4,0,.2,1);
}
.ab-10__front {
transform: translateY(0) rotateX(0);
}
.ab-10__back {
transform: translateY(100%) rotateX(-90deg);
}
.ab-10__btn:hover .ab-10__front {
transform: translateY(-100%) rotateX(90deg);
}
.ab-10__btn:hover .ab-10__back {
transform: translateY(0) rotateX(0);
}
@media (prefers-reduced-motion: reduce) {
.ab-10__front,
.ab-10__back {
transition: none;
}
.ab-10__btn:hover .ab-10__front {
transform: none;
opacity: 0;
}
.ab-10__btn:hover .ab-10__back {
transform: none;
opacity: 1;
}
}
```Here's a working CSS Animated Button from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: CSS Text Flip Button Hover
Source: https://codefronts.com/motion/css-animated-buttons/css-text-flip-button-hover/
Button labels flip vertically on hover to reveal a secondary message — built with CSS 3D perspective transforms on stacked spans, no JavaScript required.
## HTML
```html
<div class="ab-10">
<p class="ab-10__label">Hover to flip text</p>
<div class="ab-10__row">
<button class="ab-10__btn ab-10__btn--indigo">
<span class="ab-10__inner">
<span class="ab-10__front">View Pricing</span>
<span class="ab-10__back">From $9/mo →</span>
</span>
</button>
<button class="ab-10__btn ab-10__btn--teal">
<span class="ab-10__inner">
<span class="ab-10__front">Follow Us</span>
<span class="ab-10__back">@codefronts</span>
</span>
</button>
<button class="ab-10__btn ab-10__btn--rose">
<span class="ab-10__inner">
<span class="ab-10__front">Join Beta</span>
<span class="ab-10__back">Spots Left: 3</span>
</span>
</button>
</div>
</div>
```
## CSS
```css
.ab-10,
.ab-10 *,
.ab-10 *::before,
.ab-10 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.ab-10 ::selection {
background: #4f46e5;
color: #fff;
}
.ab-10 {
font-family: system-ui,sans-serif;
background: #0f0c1f;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2rem;
padding: 2rem;
}
.ab-10__label {
font-size: .78rem;
letter-spacing: .14em;
text-transform: uppercase;
color: #818cf8;
opacity: .6;
}
.ab-10__row {
display: flex;
gap: 1.25rem;
flex-wrap: wrap;
justify-content: center;
}
.ab-10__btn {
--c: #4f46e5;
padding: 0;
background: var(--c);
border: none;
border-radius: 10px;
cursor: pointer;
outline: none;
perspective: 600px;
}
.ab-10__btn--indigo {
--c: #4f46e5;
}
.ab-10__btn--teal {
--c: #0d9488;
}
.ab-10__btn--rose {
--c: #e11d48;
}
.ab-10__inner {
position: relative;
display: block;
padding: .85rem 2rem;
width: 9rem;
height: 3.1rem;
overflow: hidden;
transform-style: preserve-3d;
}
.ab-10__front,
.ab-10__back {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: .95rem;
font-weight: 700;
color: #fff;
letter-spacing: .04em;
white-space: nowrap;
backface-visibility: hidden;
transition: transform .35s cubic-bezier(.4,0,.2,1);
}
.ab-10__front {
transform: translateY(0) rotateX(0);
}
.ab-10__back {
transform: translateY(100%) rotateX(-90deg);
}
.ab-10__btn:hover .ab-10__front {
transform: translateY(-100%) rotateX(90deg);
}
.ab-10__btn:hover .ab-10__back {
transform: translateY(0) rotateX(0);
}
@media (prefers-reduced-motion: reduce) {
.ab-10__front,
.ab-10__back {
transition: none;
}
.ab-10__btn:hover .ab-10__front {
transform: none;
opacity: 0;
}
.ab-10__btn:hover .ab-10__back {
transform: none;
opacity: 1;
}
}
```How this works
Each button contains two span elements — .front and .back — stacked inside a .ab-10__inner container that has perspective: 600px. The container has a fixed height equal to one label line. In the resting state, .back is hidden beneath by transform: rotateX(90deg) and translated to sit just below the viewport of the container (via translateY(100%)). On :hover, .front rotates to rotateX(-90deg) (flipping upward out of view) while .back rotates back to 0deg, completing the vertical flip. Both transitions use the same duration and easing.
A transition-delay of 0s on the outgoing element and a matching delay on the incoming ensures the two halves of the flip feel like a single synchronized pivot. backface-visibility: hidden prevents the ghost of each panel showing through the other during mid-rotation. All transforms happen on the compositor, making the effect entirely jank-free.
Make it yours
- Change the flip axis from vertical to horizontal by replacing
rotateXwithrotateYand adjusting thetranslateXoffsets accordingly. - Increase the 3D depth by raising the
perspectivevalue from600pxto1200px— smaller values create a more dramatic foreshortening effect. - Add a colour change on reveal by transitioning the button
backgroundsimultaneously with the flip to reinforce the state change visually. - Show an icon in the back label by replacing the back
spantext with an SVG inline icon alongside the text for a "click to confirm" interaction. - Stagger multiple flipping buttons in a row by giving each
.ab-10__btnan increasinganimation-delaywhen a parent class triggers the group flip via JS.
Gotchas — read before shipping
backface-visibility: hiddenis required — without it some browsers render a mirror image of the front panel visible through the back during the rotation.- On Safari, 3D transforms inside a
position: relativebutton element can occasionally produce a rendering artefact where the flip stalls — wrapping the spans in a dedicatedtransform-style: preserve-3dcontainer resolves this. - Very short labels (< 3 characters) may not fill the button width after the flip if the back label is longer — fix by setting an explicit
min-widthon the button or usewidth: max-content.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 36+ | 9+ | 16+ | 36+ |
CSS 3D transforms with backface-visibility are well-supported; no prefixes needed in modern browsers.
Techniques used in this demo
3D transforms (perspective + preserve-3d)