DEV Community

Cover image for Create a Card Flip Animation with CSS
Andy Van Slaars
Andy Van Slaars

Posted on • Originally published at vanslaars.io

Create a Card Flip Animation with CSS

Animation, when used with restraint, can be a great way to enhance a user experience by making elements on the page feel more tangible and by adding visual interest. In this short tutorial, we'll use HTML, CSS, and just a touch of JavaScript to animate a card flipping over.

We'll be building up to the example in this CodeSandbox. Clicking the "Flip Card" button will show the opposite side of the card and do so with a nice, natural looking flip.

Edit sandpack-project

You can follow along with live examples embedded right in the article at the original source.

Initial Setup

Our card will consist of a pair of divs, we'll toggle between which div we want to show using a single CSS class, and we'll do the rest using CSS transforms. Let's setup our divs and initial CSS to get started.

Markup

<body>
<div class="container">
  <button type="button" id="flip-btn">Flip Card</button>
  <div class="card">
    <div id="back" class="cardBack">Back</div>
    <div id="front" class="cardFront">Front</div>
  </div>
</div>
</body>
Enter fullscreen mode Exit fullscreen mode

Initial Styles

Let's start by giving our elements their basic styling.

Container

This element will wrap the entire example, so we'll make it take up the whole view port and throw a light gray background on it. We'll also give it a little padding, make it a flex container, set our flex direction to columns, and center everything.

.container {
  padding: 20px;
  background-color: lightgray;
  display: flex;
  flex-direction: column;
  align-items: center;
  margin: auto;
  min-height: 100vh;
}
Enter fullscreen mode Exit fullscreen mode

Card Wrapper

Looking at the markup, we can see that this div wraps the two divs that represent the front and back of our card. We'll set a width, minimum height, and a position: relative. We'll ultimately end up setting our front and back card divs to use position: absolute. By putting the relative here, we make sure that absolute positioning is applied relative to this div.

.card {
  margin-top: 1rem;
  min-height: 300px;
  width: 250px;
  position: relative;
  border-radius: 0.25rem;
}
Enter fullscreen mode Exit fullscreen mode

Front and Back Cards

The front and back card divs will share quite a bit of their styling, so let's start with the shared styling. We're setting the box-sizing property to border-box to make sure that however we style the cards, their width and height includes any borders or padding and they fit within the parent element's width. We'll also round the corners and apply a box shadow to provide a bit of depth.

.cardFront,
.cardBack {
  box-sizing: border-box;
  border-radius: 0.25rem;
  height: 100%;
  padding-left: 0.5rem;
  padding-right: 0.5rem;
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1),
    0 2px 4px -1px rgba(0, 0, 0, 0.06);
  width: 100%;
}
Enter fullscreen mode Exit fullscreen mode

Toggle Classes with JavaScript

We don't need a framework or build tools here, in fact, there's such a small amount of JavaScript here that we'll just add it in script tags right in the HTML file. Of course, you can make this happen in your favorite framework. For this demo, let's keep it simple and add the following script tags to your HTML file. I added mine right after the closing div for our container.

<script>
  const front = document.getElementById('front')
  const back = document.getElementById('back')
  const btn = document.getElementById('flip-btn')
  function handleFlip() {
    front.classList.toggle('flipped')
    back.classList.toggle('flipped')
  }
  btn.addEventListener('click', handleFlip)
</script>
Enter fullscreen mode Exit fullscreen mode

We're going to use the flipped class on both front and back divs and we'll control the styling through that. This code uses browser APIs to add and remove the flipped class to the divs when the button is clicked.

Transform Cards Based on The flipped Class

Now that we have the flipped class being toggled on our divs, let's add some styles that are specific to the front and back cards, accounting for the flipped and non-flipped states.

Card Back Styling

We'll give the back card a background color, and then we'll use a transform to set an initial rotation. We're turning the card 180deg on the Y axis, flipping it over by default. Then when the flipped class is applied, we use the same rotateY function and flip the card to face us.

.cardBack {
  transform: rotateY(180deg);
  background-color: #ebf4ff;
}

.cardBack.flipped {
  transform: rotateY(0deg);
}
Enter fullscreen mode Exit fullscreen mode

Front Card Styling

We'll apply the same flipping behavior to the front card, but in reverse. We'll default to the card facing us, and rotate it 180deg when the flipped class is applied.

.cardFront {
  transform: rotateY(0deg);
}

.cardFront.flipped {
  transform: rotateY(180deg);
}
Enter fullscreen mode Exit fullscreen mode

Add the Flip with CSS Transitions

With our logic in place to toggle our classes, and our transforms applied, we can observe the card sections applying the updated styles and "flipping". It's quite abrupt in its current state, so let's add a transition rule to our shared card styles. We'll add transition: transform 0.5s ease; to transition our tranforms over half a second, and we'll apply the ease timing function.

.cardFront,
.cardBack {
  box-sizing: border-box;
  border-radius: 0.25rem;
  height: 100%;
  padding-left: 0.5rem;
  padding-right: 0.5rem;
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1),
    0 2px 4px -1px rgba(0, 0, 0, 0.06);
  width: 100%;
+ transition: transform 0.5s ease;
}
Enter fullscreen mode Exit fullscreen mode

With that applied, we have a nice animated transition when flipped.

Position Cards

Now we should position the cards so they behave like a single card instead of two stacked divs. Our parent container has a position: relative rule applied to it, so all we need to do is set a rule for each card element to position: absolute. Now our cards will be absolutely positioned, relative to the parent container. Our card pieces are sitting on top of one another now.

.cardFront,
.cardBack {
  box-sizing: border-box;
  border-radius: 0.25rem;
  height: 100%;
  padding-left: 0.5rem;
  padding-right: 0.5rem;
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1),
    0 2px 4px -1px rgba(0, 0, 0, 0.06);
  width: 100%;
  transition: transform 0.5s ease;
+ position: absolute;
}
Enter fullscreen mode Exit fullscreen mode

Hide the Back Side of Cards

When we flip our cards, we can still see both labels. This is because we only have a background color set on the back card, and the stacking order puts the front card on top, so we see both labels in their respective position and rotation based on that state of the flipped class.

Let's update our styling to hide the back side of the cards so we only see the card that is face up. We'll do this by adding a backface-visibility rule to our shared styles:

.cardFront,
.cardBack {
  box-sizing: border-box;
  border-radius: 0.25rem;
  height: 100%;
  padding-left: 0.5rem;
  padding-right: 0.5rem;
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1),
    0 2px 4px -1px rgba(0, 0, 0, 0.06);
  width: 100%;
  transition: transform 0.5s ease;
  position: absolute;
+ backface-visibility: hidden;
}
Enter fullscreen mode Exit fullscreen mode

Now when we flip the cards, we only see the face up card. Notice that when we can see the front card, we lose the background color that we had applied to the back card because it is hidden. We'll add a background to the front card in a future, cleanup step. For now we'll keep it so everything is clear in the UI as we progress.

We're almost done. The flip animation probably looks a little strange and unnatural at this stage. That's okay, we'll address that next.

Fix the Unnatural Flip

Perspective

To make this animation feel more natural, we'll apply some perspective to give the card a feeling of depth. I've settled on 600px here, but feel free to play around with the values and pick something that feels "right" to you.

.cardBack {
-  transform: rotateY(180deg);
+  transform: perspective(600px) rotateY(180deg);
   background-color: #ebf4ff;
}

.cardBack.flipped {
-  transform: rotateY(0deg);
+  transform: perspective(600px) rotateY(0deg);
}

.cardFront {
-  transform: rotateY(0deg);
+  transform: perspective(600px) rotateY(0deg);
}

.cardFront.flipped {
-  transform: rotateY(180deg);
+  transform: perspective(600px) rotateY(180deg);
}
Enter fullscreen mode Exit fullscreen mode

Rotation Direction

Something is still off, our card flip still doesn't feel right. The remaining problem is the direction the cards flip in. They're essentially crashing through each other to swap rotations. We need them to rotate as a single unit, so we'll have to make one of the cards go in the opposite direction.

As it turns out, the transition algorithm will do some math and you can manipulate that math to control the direction the object moves in when transitioning. All we need to do is change our front card from 180deg when flipped to -180deg. It doesn't change the end result, but it does change the way the transition is applied.

.cardFront.flipped {
-  transform: perspective(600px) rotateY(180deg);
+  transform: perspective(600px) rotateY(-180deg);
}
Enter fullscreen mode Exit fullscreen mode

Final cleanup

All we have left is to apply a background color to the front card so our card isn't transparent from the front. You can do whatever you want in terms of color, background image, etc. Here we'll keep it simple and just apply the same background to both cards by moving our background-color rule from the cardBack-specific rules to our shared card styles.

.cardFront,
.cardBack {
  box-sizing: border-box;
  border-radius: 0.25rem;
  height: 100%;
  padding-left: 0.5rem;
  padding-right: 0.5rem;
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1),
    0 2px 4px -1px rgba(0, 0, 0, 0.06);
  width: 100%;
  transition: transform 0.5s ease;
  position: absolute;
  backface-visibility: hidden;
+ background-color: #ebf4ff;
}

.cardBack {
- background-color: #ebf4ff;
  transform: perspective(600px) rotateY(180deg);
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

This is a pretty common interaction to animate and CSS makes it fairly simple to achieve. No need to complicate things with too much JavaScript. By using CSS for this, all the tricky logic and things we might have otherwise missed are handled for us by the browser. For example, if we toggle the flipped class part way through a transition, it naturally changes direction without any jarring flickers or jumps. It just picks up the new value calculations from where you left off. Go ahead, click the flip buttons a whole bunch of times really fast... the card will flip flop and finish as expected without awkwardly jumping to the far end of the animation at any point. This is absolutely something that we could build with JavaScript, but not necessarily something we should build with JavaScript. By using a CSS based approach, we cut down our initial work, reduce the need for maintenance, and gain some performance by letting the browser handle this for us.

BTW, If you like to learn with bite-sized videos, I published this very thing as a lesson on egghead.io a while back, check it out!

Top comments (0)