What Is a CSS Flip Card?
The front usually carries an image or a headline, and the back carries the detail, like a bio, a price, a definition or a spec. Point at the card (or tap it, or press a key, depending on how it’s wired) and it turns over in 3D to show the other side. CSS transforms and a transition do all of it, with no animation library involved.
Structurally there’s a container that sets the card size and holds the perspective. Inside it sits a wrapper, and the wrapper is the element that actually rotates. The front face is what you see at rest. The back face is pre-rotated 180 degrees, so it stays hidden until the flip.
W3Schools names these .flip-card, .flip-card-inner, .flip-card-front and .flip-card-back, and a Leyaa guide uses the same scheme. Other tutorials pick their own class names.
The whole structure is plain HTML. Four elements, no script, no required images.
This isn’t a fade or a slide between two panels, either. MDN notes that backface-visibility has no effect on 2D transforms, which means a flat rotate with no perspective can’t give you the two-sided look.
How to Build a CSS Flip Card Step by Step
Work through these in order. Skipping one tends to fail without any error, which makes it annoying to debug.
- Give
.flip-card a width, a height and perspective: 1000px. - Give
.flip-card-inner position: relative, transition: transform 0.6s and transform-style: preserve-3d. - Set both faces to
position: absolute, full width and height, with backface-visibility: hidden. - Add
transform: rotateY(180deg) to .flip-card-back. - Rotate the wrapper to
rotateY(180deg) inside a trigger rule such as .flip-card:hover .flip-card-inner.
Markup first.
<div class="flip-card">
<div class="flip-card-inner">
<div class="flip-card-front">Front</div>
<div class="flip-card-back">Back</div>
</div>
</div>
And the styles.
.flip-card {
width: 300px;
height: 200px;
perspective: 1000px;
}
.flip-card-inner {
position: relative;
width: 100%;
height: 100%;
transition: transform 0.6s;
transform-style: preserve-3d;
}
.flip-card:hover .flip-card-inner {
transform: rotateY(180deg);
}
.flip-card-front,
.flip-card-back {
position: absolute;
width: 100%;
height: 100%;
-webkit-backface-visibility: hidden; /* Safari */
backface-visibility: hidden;
}
.flip-card-back {
transform: rotateY(180deg);
}
The perspective property goes on the container, not on the wrapper that rotates. The fullPage card flip guide points out that setting it on the inner wrapper has no effect on that wrapper’s own rotation.
Skip step 2 and both faces collapse into one plane. The fullPage guide and a Leyaa guide both flag this failure. The container also needs a defined width and height, since Metro UI’s flip card docs list that as a requirement.
That -webkit- line is a Safari leftover, needed only before Safari 15.4 (March 2022). W3Schools still ships it in its flip card example as of October 2026.
Which Perspective, Duration and Axis Values Work Best?
Start with a perspective around 3 to 4 times the card width, a transition between 0.3s and 0.8s, and rotateY for a side-to-side turn. The tutorials that state exact values all land inside those ranges.
Here’s what the published ones actually use, as read in October 2026.
- W3Schools sets perspective at 1000px with a 0.8s transition on a card that’s 300 by 200 pixels.
- The DEV Community tutorial goes with perspective 600px and a 0.4s transition (0.3s on the hover rule), on a card 150 by 150 pixels.
- The fullPage card flip guide calls about 800px a sweet spot and suggests 300 to 500px for a dramatic rotation.
- Metro UI’s flip card defaults to an animation speed of 0.3s.
Divide each perspective by its card width and a pattern shows up: 3.3 for W3Schools, 4 for the DEV Community tutorial. Two data points make a starting range, not a law.
MDN covers which way the effect runs. A large perspective value gives a small transformation, and a small value gives a big one.
Horizontal or vertical flip
For a horizontal flip, put rotateY(180deg) on the wrapper and pre-rotate the back face by the same rotateY(180deg). A vertical flip swaps both rotations to rotateX(180deg), and Daily Dev Tips publishes a vertical variant next to its horizontal one. Use a negative angle such as rotateY(-180deg) and the card turns the other way, as the fullPage guide explains.
Keep the back face pre-rotation on the same axis as the trigger rotation. Mix them and the back text reads mirrored.
Easing and duration
Duration sets how long the turn takes, and the timing function sets how the speed changes along the way. Preview both in the browser before you commit values to the stylesheet.
MDN’s keyword options for transition-timing-function include ease, ease-in, ease-out, ease-in-out and linear.
A Treehouse CSS challenge uses ease-in-out as the timing function that starts and stops gradually, which suits a card that settles into place. Stay at or under 0.8s, since that’s the longest value among the sources above and anything slower drags.
Hover, Focus, Click or Checkbox: Which Trigger Fits?
Pick the trigger by the weakest input device your visitors use. A class toggle on a real button covers mouse, touch and keyboard, while a hover-only flip fails on touch screens.
| Trigger | Touch screens | Keyboard | Needs JavaScript |
|---|
| :hover | Unreliable | No | No |
| :focus-within | Browser dependent | Yes, with tabindex | No |
| Checkbox hack | Yes, tap the label | Yes, if the input stays focusable | No |
| Class toggle | Yes | Yes, on a button | Yes |
Hover is the simplest of the CSS hover effects to wire up, a single rule on the container.
The Calgary design system warns against relying on hover alone, and the fullPage guide notes that the :hover selector does not work on touch devices. A June 2021 CSS-IRL article adds a wrinkle. Some Android versions emulate hover on a long press, so (hover: hover) alone can evaluate true on a phone. Pair it with (pointer: fine).
Focus is the next option. The :focus-within pseudo-class matches an element when it or any descendant has focus, and MDN lists it as available across browsers since January 2020. A div isn’t focusable by default, so the card needs tabindex="0" (Quackit’s 3D card flip example does this).
W3C’s Understanding document for WCAG 2.2 criterion 1.4.13 states that content triggered by pointer hover should also be triggerable by keyboard focus.
The checkbox hack flips the card on tap with no script at all. Wrap the card in a label tied to a hidden checkbox, then use a :checked sibling selector to rotate it. FreeFrontend lists several pure CSS cards and decks built this way.
Hide the input visually, not with display: none, or it drops out of the tab order.
With a class toggle, a few lines of JavaScript add a .flipped class on click, and the CSS rule mirrors the hover rule. The fullPage guide recommends this route when click-to-flip does not work with CSS alone.
So hover only makes sense when the back face holds nothing a visitor needs. For anything that matters, use a class toggle on a button. If scripts are off the table, focus-within or the checkbox hack will do.
How to Make a CSS Flip Card Accessible
No single attribute makes a flip card accessible. A keyboard has to be able to trigger it, a screen reader has to know which face is showing, the inactive face shouldn’t be read aloud, and visitors who ask for less motion shouldn’t get the rotation.
Keyboard and focus
The City of Calgary design system tells teams to give each interactive card role="button" or role="group", add tabindex="0", and make Enter and Space flip it.
James Edwards, writing for Vispero in September 2025, takes a different line. He says never to put role="button" on the card or its face containers, because it cancels the semantics of everything inside, such as headings and links, for assistive technology. A real button inside the card is the better route. A focusable div with a button role looks simpler, but it hides the content the card exists to show.
Sources also split on state. A Colorlib template released in September 2026 exposes the flip through a toggle button with aria-pressed, while Vispero found that ARIA state attributes such as aria-pressed and aria-expanded do not describe a card that is never pressed or expanded. Regular screen reader users who tested demos built on them found them confusing.
Vispero’s fix is a button whose label alternates between “Flip to back” and “Flip to front”, plus a description of the face now showing. iOS VoiceOver does not support dynamic description updates, so a static aria-describedby stays as the fallback.
Screen readers and the hidden face
Give the inactive face display: none, which removes it from the accessibility tree. Vispero does this, and the Colorlib template also removes its hidden face from that tree. backface-visibility: hidden only controls painting, so it can’t do that job alone.
Vispero also swaps the visible face at the 90 degree point of the rotation, when neither face can be seen.
That last trick means only one face exists at a time, so the faces need no absolute positioning, no fixed height and no matching size. The price is a JavaScript class that watches the rotation with requestAnimationFrame() and getComputedStyle().
Reduced motion
MDN names scaling and panning of large objects as vestibular motion triggers, and a rotating card is a large object in motion.
A media query on prefers-reduced-motion lets those visitors skip the rotation while everyone else keeps it.
@media (prefers-reduced-motion: reduce) {
.flip-card-inner {
transition: none;
}
}
That snippet is the snap approach. The flip still happens, instantly, and Quackit’s 3D card flip example works this way. Crossfading swaps the faces into each other instead of rotating, which is what the Colorlib template does. Gating puts the rotation rules only inside (prefers-reduced-motion: no-preference), so a plain face swap is the default, and Vispero builds it this way.
Gating is the safest of the three, because a browser that cannot evaluate the query never plays the rotation.
How to Lay Out Flip Cards in a Responsive Grid
A CSS grid with repeat(auto-fit, minmax(240px, 1fr)) columns does most of the work. Each card gets its height from its width through aspect-ratio, and the columns reflow on their own as the viewport narrows.
.flip-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 1.5rem;
}
.flip-card {
aspect-ratio: 3 / 2;
perspective: 1000px;
}
The faces are absolutely positioned, so the card needs a height the grid can read. The aspect ratio supplies one from the column width, and a fixed height works too.
No breakpoint decides the column count here, which makes it the lowest-effort route to responsive design for a card collection.
Vispero’s review of flip card implementations says absolute positioning with fixed sizes stops the content from responding to larger fonts or text spacing, so one or both faces overflow the container. The City of Calgary design system caps rotating cards at 4 per row, because crowded layouts hurt focus and mobile usability.
Perspective units matter too. A pixel value suits one card width, and grid columns stretch. Vispero sets perspective: 64rem, which follows font size instead of column width.
Measure the longest back face before choosing the ratio. If it needs more room than the ratio allows, shorten the copy or change the pattern.
Do CSS Flip Cards Work in Every Browser?
Chrome, Edge, Firefox and Safari all run a flip card without extra code. Can I use lists Internet Explorer 10 and 11 and Opera Mini as the gaps.
| Property | MDN Baseline status | Job in the card |
|---|
| perspective | Widely available; across browsers since September 2015 | Depth on the container |
| transform-style | Widely available; across browsers since September 2015 | Keeps both faces in 3D |
| backface-visibility | Widely available; across browsers since March 2022 | Hides the reversed face |
Can I use marks IE 10 and 11 as partial support in its 3D transforms entry, and its note explains the label: neither browser supports transform-style: preserve-3d.
A flip card depends on preserve-3d, so partial support in IE still means a broken card.
Treat the IE gap as a decision, not a bug. Cross-browser compatibility testing on the browsers you actually ship to tells you whether a fallback is worth building, such as swapping faces with plain display rules.
MDN’s compatibility notes also record that the perspective property did not function properly on iOS 13, with the issues fixed in iOS 14.
Since the unprefixed property has Baseline status, projects that target current browsers can drop -webkit-backface-visibility. Keep it only for Safari 9 to 15.3.
Why Is a CSS Flip Card Not Working?
Nearly every failure traces back to a broken 3D context. Usually the wrapper is flattened by another property, a nested element is missing preserve-3d, or a stacking context traps the faces.
| Symptom | Cause | Fix |
|---|
| Faces flat although preserve-3d is set | Wrapper has overflow other than visible or clip, opacity below 1, a filter, or isolation: isolate | Move those properties to the container or a face |
| Faces flat after adding a wrapper | transform-style is not inherited | Set preserve-3d on every non-leaf element above the faces |
| z-index ignored between cards | perspective and preserve-3d each create a stacking context | Raise the container, not its children |
| Back text spills out of the card | Fixed height with absolutely positioned faces | Scroll inside the face with overflow-y: auto, never on the wrapper |
| Text looks soft in Safari | Inside a scaled parent, a 3D-rotated sibling makes text render as a scaled bitmap | No confirmed CSS fix |
The first three rows come from MDN’s documentation of transform-style and perspective. The overflow row follows from the first, since the faces are leaf elements and the wrapper is not.
The Safari row rests on a WebKit Bugzilla report (278043, August 2024, Safari Technology Preview 199 on macOS 14.6). It lists the bug as new and notes that Chrome and Firefox render the same page sharply.
When a flip card is the wrong choice
- Information visitors need sits on the back face, and keyboard and screen reader users cannot reach it (the City of Calgary design system advises against this).
- The back content varies in length or has to survive text zoom, where an accordion reveals the same detail in normal flow.
If most visitors need the back face, put its content on the front.
CSS Flip Card FAQ
Can I build a CSS flip card with Tailwind CSS?
Yes. Tailwind CSS 4 covers every property the card needs. transform-3d sets preserve-3d, perspective-* sets depth, rotate-y-* turns the faces, and backface-hidden hides the reversed side. Earlier versions lack these 3D utilities.
Does Bootstrap include a flip card component?
No. Bootstrap’s card component is a content container with headers, footers and images, and its documentation describes no flip behavior.
Add the rotation with custom CSS on top of the card markup, and use Bootstrap’s grid to wrap the cards in columns.
Is the GSAP Flip plugin the same as a CSS flip card?
Only the name is shared. The GSAP Flip plugin records an element’s state, lets the DOM change, then animates the difference, which suits layout transitions in grid and flexbox.
A rotating card built with GSAP still tweens a rotation.
How do I put an image on the front and text on the back?
Place the img inside the front face and the heading and text inside the back face. W3Schools’ flip card example does exactly this, with an avatar in front and a name and title behind it.
Size the image to the face with object-fit: cover.
How do I make the card spin a full 360 degrees?
Change the trigger rotation from 180deg to 360deg. A DEV Community tutorial notes this suits single-sided buttons, because a two-faced card finishes the turn showing its front again and the back face appears only mid-spin.
Which content suits a CSS flip card?
Anything where the front identifies something and the back adds optional detail. Team member photos with a bio behind them are the classic case. Product images with specs work too, and so do flashcards that put a term on the front and its meaning on the back.
Can a CSS flip card flip on its own with keyframes?
Yes, an animation built from CSS keyframes can rotate the inner wrapper on a timer.
The City of Calgary design system advises against it, because motion that starts without user interaction is unpredictable and disorienting.
How to Test a CSS Flip Card Before Launch
Testing comes down to whether the back face stays reachable under every input and setting your visitors use. That means mouse, keyboard, touch, a screen reader and reduced motion.
Run the checks in order of how many visitors a failure blocks.
- Tab to the card and press Enter and Space.
- On a touch screen, tap once to flip and once to return.
- Confirm a screen reader announces one face at a time.
- Switch the operating system’s reduced motion setting on and flip again.
Reach comes before announcement and motion, since an unreachable back face is lost to a whole group of visitors however polished the animation is.
The cost is four passes per card pattern, and a grid only pays it once because every card shares the pattern.
Pages with more interactive components than cards can run the same passes through a web accessibility checklist.