CSS 3D Card Flip
A card that flips on hover using CSS 3D transforms and perspective.
A CSS card flip turns a panel over to reveal a second face on its back, and it needs four properties to work: perspective, transform-style, backface-visibility, and a rotateY transform. No JavaScript is involved. The browser handles the rotation on the compositor, so the animation stays smooth even on a page that is busy doing other work.
The pattern shows up on pricing tables, team photos, product tiles, and flashcards. Below are three working versions: the standard horizontal flip on hover, a vertical flip on the X axis, and a click driven flip that works on phones and with a keyboard. Each one is the same technique with a different trigger.
Flip on hover
Hover to flip
Pure CSS
Hover me
HTML
<div class="flip-card">
<div class="flip-inner">
<div class="flip-front">🎨 Front Side</div>
<div class="flip-back">✨ Back Side</div>
</div>
</div>
CSS
.flip-card {
width: 200px;
height: 130px;
perspective: 800px;
}
.flip-inner {
width: 100%;
height: 100%;
position: relative;
transform-style: preserve-3d;
transition: transform .55s ease;
}
.flip-card:hover .flip-inner {
transform: rotateY(180deg);
}
.flip-front,
.flip-back {
position: absolute;
inset: 0;
border-radius: 12px;
backface-visibility: hidden;
display: flex;
/* stacked, so a <br> between title and sub label actually breaks */
flex-direction: column;
align-items: center;
justify-content: center;
padding: 1rem;
text-align: center;
font-size: .95rem;
font-weight: 600;
}
.flip-front {
background: #b8ff57;
color: #0c0c0d;
}
/* Pre-rotated so it faces the viewer once the card turns */
.flip-back {
background: #c084fc;
color: #0c0c0d;
transform: rotateY(180deg);
}
Other ways to build it
Vertical flip
Rotating on the X axis turns the card top over bottom instead of left to right. Only two values change, and they have to change together: the hover rule and the pre rotation on the back face both move from rotateY to rotateX. Leave one behind and the back face arrives upside down.
Hover to flip
rotateX
HTML
<div class="flip-card flip-vertical">
<div class="flip-inner">
<div class="flip-front">Front<br><small style="font-weight:400;opacity:.8">Hover to flip</small></div>
<div class="flip-back">Back<br><small style="font-weight:400;opacity:.8">rotateX</small></div>
</div>
</div>
CSS
.flip-card:hover .flip-inner {
transform: rotateX(180deg);
}
.flip-back {
transform: rotateX(180deg);
}
Flip on click, for touch and keyboard
A hidden checkbox holds the open state and a label wraps the card, so tapping or pressing space turns it over. This is the version to reach for when the back face carries content the reader actually needs, because hover reaches neither touchscreens nor keyboards. The focus ring sits on the label, since the checkbox driving it is visually hidden.
HTML
<div class="flip-card">
<input type="checkbox" id="card-1" class="flip-toggle">
<label for="card-1" class="flip-click">
<div class="flip-inner">
<div class="flip-front">Tap to flip</div>
<div class="flip-back">Back face</div>
</div>
</label>
</div>
CSS
.flip-toggle {
position: absolute;
opacity: 0;
width: 1px;
height: 1px;
}
.flip-click {
display: block;
/* the label sits between the sized card and .flip-inner, so it has
to carry the box or the inner height: 100% resolves to zero */
width: 100%;
height: 100%;
cursor: pointer;
}
.flip-toggle:checked + .flip-click .flip-inner {
transform: rotateY(180deg);
}
/* the checkbox is hidden, so the focus ring goes on the label */
.flip-toggle:focus-visible + .flip-click .flip-inner {
outline: 2px solid #b8ff57;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.flip-inner { transition: none; }
}
How it works
The container sets perspective to create the 3D space. The inner element holds both faces and uses transform-style:preserve-3d so children exist in the same 3D plane. The back face is pre-rotated 180deg and both faces have backface-visibility:hidden so the correct face shows at each point of the flip.
Three elements do the work. The outer .flip-card is a plain box that never moves. Its only job is perspective, which tells the browser how far the viewer is from the screen. A small value like 400px gives a dramatic, almost fisheye rotation. A large value like 2000px flattens the effect until it barely reads as 3D at all. The 800px used here sits in the middle.
The middle element is the one that rotates. It carries transform-style: preserve-3d, without which the browser flattens its children into a single plane and the back face never appears. This is the property people most often leave out, and the symptom is a card that fades or squashes rather than turning.
The two faces are stacked on top of each other with position: absolute and inset: 0. The back face is rotated 180 degrees in the stylesheet, before any interaction, so that when the parent turns 180 degrees the back arrives facing the viewer the right way round. backface-visibility: hidden hides whichever face is currently pointing away, which is what stops the two from showing through each other mid turn.
Because the whole effect is one transform, the browser can run it on the compositor thread rather than recalculating layout. That is why the flip stays smooth where an equivalent effect built from width and height changes would stutter.
CSS properties used
perspective- Sets how far the viewer is from the z=0 plane. Smaller values exaggerate the rotation, larger values flatten it. Goes on the parent, not the rotating element.
transform-stylepreserve-3dkeeps child elements in their own 3D positions. Without it the faces collapse flat and the back never shows.backface-visibilityhiddenstops an element being drawn when its reverse side faces the viewer. Applied to both faces so only one is ever visible.transformrotateY(180deg)turns the card horizontally,rotateX(180deg)turns it vertically. The back face carries a matching pre rotation.transition- Animates the transform between states. Without it the card swaps faces instantly.
Browser support
| Feature | Chrome | Firefox | Safari | Edge |
|---|---|---|---|---|
transform-style | 12 | 10 | 4 | 12 |
backface-visibility | 12 | 10 | 4 | 12 |
transition | 4 | 5 | 5.1 | 12 |
Support figures come from Can I Use for CSS 3D Transforms and CSS Transitions. Every browser in current use handles this, so no fallback is required. Safari has historically needed -webkit-backface-visibility on older versions, though not on any release from the last several years.
Accessibility notes
A hover flip is unreachable for anyone using a keyboard, and on a touchscreen it either does nothing or fires on the first tap in a way people do not expect. If the back face holds information the reader needs, use the click driven version below, which is a checkbox and a label, so it is focusable and operable with the space bar.
Content on the back face stays in the accessibility tree even while it is turned away, because backface-visibility only affects painting. A screen reader will announce both faces. When the back is purely decorative, mark it aria-hidden="true". When it holds real content, that announcement is usually what you want.
The stylesheet includes a prefers-reduced-motion block that removes the transition. The card still changes face, it just stops spinning to get there. Rotation is a common migraine and nausea trigger, and honouring that setting costs three lines.
What you can build with it
- Pricing tiers. Plan name and price on the front, the full feature list on the back, which keeps a comparison table short without hiding the detail behind a separate page.
- Team pages. A photo on the front and a short bio with contact links on the back.
- Flashcards. Question on the front, answer on the back. The click version is the right trigger here, since the reader should decide when to reveal.
- Product tiles. Product shot on the front, specifications or a size chart on the back.
- Statistic cards. A headline number on the front and the source or methodology behind it on the back.
Mistakes worth avoiding
- Leaving
transform-style: preserve-3doff the rotating element. The card appears to squash instead of turn, which is the single most common reason a flip looks wrong. - Putting
perspectiveon the same element that rotates. It belongs on the parent. On the rotating element it produces a much weaker effect that changes as the card turns. - Forgetting to pre rotate the back face. The back arrives mirrored, and any text on it reads backwards.
- Giving the outer card no fixed height. Both faces are absolutely positioned and therefore contribute nothing to the parent's height, so the card collapses to zero.
- Relying on hover alone. Roughly half of all traffic is touch, and those readers never see the back.
Frequently asked questions
Does a CSS card flip work on mobile?
Why is my card squashing instead of flipping?
transform-style: preserve-3d. Without it the browser flattens both faces into one plane, so instead of turning through 3D space the card scales horizontally down to nothing and back.Can I flip a card vertically?
rotateY for rotateX on both the hover rule and the pre rotated back face. Both values have to change together, otherwise the back face arrives upside down.Is a hover flip accessible?
prefers-reduced-motion.