DEV Community

Cover image for How to Create a Flip Card Using CSS? (Learn 'Exactly' How It Works)
Codeguage
Codeguage

Posted on • Updated on • Originally published at codeguage.com

How to Create a Flip Card Using CSS? (Learn 'Exactly' How It Works)

Table of Contents

  1. Introduction
  2. The HTML of the card
  3. Styling the card sides
  4. Backface visibility
  5. Establishing a 3D rendering context
  6. The final transition
  7. Improving the effect

    1. Adding depth using perspective
    2. Fixing the :hover issue

Introduction

With the advent of CSS 3D transformations in the mid 2000s, a great number of 3D transitions became possible on the web without having to pull up tedious codes utilizing WebGL or some old-school graphics plugin.

One such transition is the flip card transition, demonstrated below:

In this article, we shall understand how exactly to implement a flip card transition using just pure HTML and CSS. The tools we'll be using include perspective, rotateY(), backface-visibility, transform-style and obviously transition.

This how-to guide is not like any other guide, where you're just given a code snippet to follow; you'll instead get to understand each and every aspect of implementing a 3D flip card transition.


The HTML of the card

First things first, we have to set up the HTML for the flip card.

Here's an overview of the elements we need:

  • An element representing the front side of the card.

  • An element representing the back side of the card.

  • A container element holding the above two elements.

Let's write the HTML for these elements.

We'll call the container .card. The front side will be .card_front and the back side will be .card_back. This nomenclature follows the BEM naming convention, in a slightly modified way.

Here's our simple and sleek HTML:

<div class="card">
   <div class="card_front">Front</div>
   <div class="card_back">Back</div>
</div>
Enter fullscreen mode Exit fullscreen mode

For now, for the sake of keeping things simple, we've put the text 'Front' on the front side of the card and the text 'Back' on the back side of the card.

Now, let's move to the CSS part, starting with making both .card_front and .card_back look like the sides of a card.


Styling the card sides

It's impossible to have a card with two differently-sized sides, right?

Likewise, the very first thing we'll do in the CSS is to style the front and back sides of the cards identically.

Here's the CSS we get thus far:

.card {
   border: 2px solid black;
}

.card_front,
.card_back {
   height: 150px;
   width: 120px;
   border-radius: 6px;

   /* Just styling the text nicely */
   line-height: 150px;
   font-family: sans-serif;
   font-size: 20px;
   text-align: center;
}

.card_front {
   background-color: yellow;
}

.card_back {
   background-color: pink;
}
Enter fullscreen mode Exit fullscreen mode

Notice the border applied to .card — this is temporary and only meant to see how much space the .card element spans. Once we're done with this section, we'll remove this rather bogus style.

Now, let's overlap the card sides on top of each other.

The purpose of overlapping is quite obvious — in a real card, the sides are clearly one top of each other, and so to create a card graphically, we ought to do the same thing.

For the overlap, we'll shift the absolute width and height settings to .card, and instead get the card sides to fill up the width and height of the parent .card container.

Here's the new CSS:

.card {
   border: 2px solid black;
   width: 120px;
   height: 150px;
   position: relative;
}

.card_front,
.card_back {
   position: absolute;
   height: 100%;
   width: 100%;
   border-radius: 6px;

   /* Just styling the text nicely */
   line-height: 150px;
   font-family: sans-serif;
   font-size: 20px;
   text-align: center;
}
Enter fullscreen mode Exit fullscreen mode

And here's the output we get for it:

Looks like a decent card, doesn't it?

As promised, with the <.card> element correctly styled, we'll now remove the border style from it:

.card {
   /* border: 2px solid black; */
   width: 120px;
   height: 150px;
   position: relative;
}
Enter fullscreen mode Exit fullscreen mode

And here's the output we get for it:

Let's move on.


Backface visibility

Currently, as you can see, the backside of the card (which comes later in the HTML source code) is being displayed while the frontside is hidden behind it.

In a real card, as we know, the backside is rotated by 180 degrees around the y-axis (if we dissect the card). So first off we need to rotate the backside and then do another additional thing.

Let's perform the rotation.

.card_back {
   background-color: pink;
   transform: rotateY(180deg);
}
Enter fullscreen mode Exit fullscreen mode

And here's the output we get for it:

Notice how we don't need to use any application of perspective in order to rotate the backside. This is because we are only concerned with the actual rotation itself and not with a transition taking us to that rotated state.

Now over to the additional thing.

Initially, we ideally want to hide the backside and only show the frontside of the card. To do so, we might think of using the z-index property on the frontside and thus get it stacked above the backside.

While, yes, z-index will get the frontside to be shown and the backside hidden, it isn't going to give us the correct result in the longer run, once we flip the card.

Following is a quick demonstration of what we mean by this; you don't obviously need to write the CSS shown here:

.card_front {
   background-color: yellow;
   z-index: 1;
}
Enter fullscreen mode Exit fullscreen mode

And here's the output we get for it:

With the z-index applied on the frontside, and thus the frontside stacked above the backside, we do see the backside initially.

But when we flip the whole card, by virtue of 180 degrees rotation around the y-axis, we still see the frontside.

To many, this might seem counter-intuitive and rightly so — the backside is behind the card so rotating the entire card by 180 degrees should expose the backside.

Here's why we get this weird but correct behavior:

If an element A is stored above another element B, then no matter which kind of rotation we do, the stacking will remain the same. For CSS engines, it's easier and practical to manage elements on a webpage this way rather than changing the stacking order with every 3D rotation transformation.

What we really need here is the backface-visibility property.

As the name suggests, backface-visibility specifies the visibility of the backside of an element.

By default, it's set to the value visible which means that the backside is visible to us (appearing as a mirror image). But using the value hidden, we can specify the backside to be hidden instead.

📖 Further reading:
To learn more about how backface-visibility works, refer to the following chapter from our CSS course: CSS 3D Transformations — Backface Visibility.

In our case, we'll set backface-visibility: hidden on both the card's frontside and the backside.

Here's why:

Initially, the backside is stacked on top of the frontside, likewise, it'll be shown. But because it's rotated and, in effect, its backside is facing us, we can use backface-visibility: hidden to hide it in this initial state.

Similarly, when the card is rotated, the frontside gets rotated as well, leading to its backface exposed to us. Using backface-visibility: hidden again, we can hide it in this flipped state of the card.

Simple.

backface-visibility on the frontside

Note that, strictly speaking, backface-visibility: hidden isn't required on the card's frontside.

That's because, in the flipped state, when the card's backside shows up, due to its stacking in the z-axis (remember it appears later in the HTML source code), it covers the frontside behind it.

By using backface-visibility: hidden on the frontside as well, we just make sure that if, for instance, the backside has a background color applied to it with an alpha opacity value, we don't get to see the frontside through it.

Here's the code we get with the addition of backface-visibility to .card_front and .card_back:

.card_front,
.card_back {
   position: absolute;
   height: 100%;
   width: 100%;
   border-radius: 6px;
   backface-visibility: hidden;

   /* Just styling the text nicely */
   line-height: 150px;
   font-family: sans-serif;
   font-size: 20px;
   text-align: center;
}
Enter fullscreen mode Exit fullscreen mode

And here's the output we get for it, supposing that .card isn't rotated:

Now, let's flip the .card, and see the output:

.card {
   width: 120px;
   height: 150px;
   position: relative;
   transform: rotateY(180deg);
}
Enter fullscreen mode Exit fullscreen mode

And here's the output we get for it, supposing that .card isn't rotated:

What? We still don't get the correct output!

Well, that's because we are missing one important property from the .card element, to get the same 3D space to be shared by .card, .card_front and .card_back — the transform-style property.


Establishing a 3D rendering context

In the code above, when we rotate the .card element by 180 degrees, we don't get the desired flipped state, where the card's backside is shown to us.

This is not because backface-visibility isn't in action but rather because the rotation of the card doesn't constitute a rotation of .card_front and .card_back.

Currently, .card_front and .card_back could be thought of as embedded inside the .card element. So when .card is rotated, the rendering seems as if .card_front or .card_back have been rotated themselves, but that's surprisingly NOT the case.

The card's front and back sides are rendered into the plane of .card, each having its own 3D space.

What we want here is for the card and its sides to share the same 3D space so that when we rotate the card, the sides get rotated themselves as well.

If you've read the chapter CSS 3D Transformations — The transform-style Property from our CSS course, you'll know how to do this. That is, we need the transform-style property.

transform-style, set to the value preserve-3d, gets the underlying element to establish a 3D rendering context. This effectively results in the elements to share a common 3D space.

For our case, we want .card, .card_front and .card_back to share the same 3D space. Hence, we'll set transform-style: preserve-3d on the .card element.

Let's do this now and see the result it gives to the card's initial state:

.card {
   width: 120px;
   height: 150px;
   position: relative;
   transform-style: preserve-3d;
}
Enter fullscreen mode Exit fullscreen mode

Not anything new here.

Let's now see the card's flipped state, which is where our anticipation lies:

.card {
   width: 120px;
   height: 150px;
   position: relative;
   transform-style: preserve-3d;
   transform: rotateY(180deg);
}
Enter fullscreen mode Exit fullscreen mode

Perfect! The backside shows in the flipped card.

The final thing left to do now is to add a :hover transition to the card, flipping it using a 3D rotation.


The final transition

When we hover over the card, we want it to be flipped smoothly. Clearly, this means to use transition.

In the following code, we add a 1s transition to the .card element, with the default ease transition function:

.card {
   width: 120px;
   height: 150px;
   position: relative;
   transform-style: preserve-3d;
   transition: 1s ease;
}
Enter fullscreen mode Exit fullscreen mode

The hover state should have the .card rotated by 180 degrees:

This is accomplished below:

.card:hover {
   transform: rotateY(180deg);
}
Enter fullscreen mode Exit fullscreen mode


Improving the effect

Technically, our flip card transition is done but there are two problems with it:

  1. One is that there isn't any depth in the rotation.

  2. The other is the fact that the :hover state is applied on the element which itself is rotated.

Let's fix both these issues and create a flawless flip card effect.

Adding depth using perspective

Let's slow down the transition above to be able to clearly visualize what we mean by there being no depth in our rotation.

We'll change the transition duration from 1s to 5s:

.card {
   width: 120px;
   height: 150px;
   position: relative;
   transform-style: preserve-3d;
   transition: 5s ease;
}
Enter fullscreen mode Exit fullscreen mode

Hover over the following <div>:

As you can see, the rotation appears completely flat; it does not give us the impression of being in a 3D setting.

To alleviate this issue, we just need to add perspective to bring depth into our rotation effect.

For this, we'll use the perspective() transform function on the .card element, before the rotateY() function in the transform property in its :hover state.

Here's the new code we get:

.card:hover {
   transform: perspective(400px) rotateY(180deg);
}
Enter fullscreen mode Exit fullscreen mode

The argument provided to perspective() specifies the distance between the view point and the screen (sometimes referred to as the z=0 plane). The larger the distance, the less pronounced is the 3D effect.

📖 Further reading:
To understand perspective() in detail, refer to the chapter, CSS 3D Transformations — Perspective, from our CSS course.

And here's the output:

See? That's the depth we were talking about.

Fixing the :hover issue

With the same slow card transition in place, try hovering over the card and then as it reaches its midway, take the mouse pointer out of it.

Notice one strange, albeit correct, thing as this happens. As soon as the pointer leaves the card amid its rotated state, the transition gets reverted back.

This is simply because the :hover state is applied to the .card element and it's also the .card that gets rotated. With the rotation, the interactive region of the card changes and, thus, as we move the pointer out of the card (in this rotating transition), it counts as leaving the card.

To alleviate this issue, we need to separate the :hover state and the rotation from the same element.

For this, we'll create a wrapper around our .card element; let's call it .card-cont:

<div class="card-cont">
   <div class="card">
      <div class="card_front">Front</div>
      <div class="card_back">Back</div>
   </div>
</div>
Enter fullscreen mode Exit fullscreen mode

The :hover state will now be attached to .card-cont while the rotation will be performed, as before, on .card.

In this way, while the card is rotating, and we move the pointer out of its region (in the rotation), the transition would continue happening since the pointer would still be inside .card-cont and :hover is attached to .card-cont.

Following is the CSS code:

.card-cont {
   display: inline-block;
}

.card-cont:hover .card {
   transform: perspective(400px) rotateY(180deg);
}
Enter fullscreen mode Exit fullscreen mode

The display: inline-block style is given to get the .card-cont element to fit the .card element and not span the entire available width.

Let's see the output:

From a distant view, this small change allows us to prevent seemingly-snappy flip card effects (where the pointer's movement amid the card's transition is the real culprit).

To end with, let's revert back our transition duration to 1s to get our nice and quick flip card effect:

And this is our flip card effect.


More about CodeGuage

At CodeGuage, we offer absolutely free-of-cost courses on CSS, JavaScript, Advanced JavaScript, AJAX, and React, to get better at frontend development. In addition, many more courses are planned to be introduced in the near future.

You can follow us on Twitter in order to stay updated with all of the latest endeavors happening on our website.

And if you enjoyed reading this article, don't forget to ❤ it and share it with others.

Top comments (0)