Create a Card Flip Animation with CSS
Published: 2022-03-07 17:09:09

---
title: Create a Card Flip Animation with CSS
published: true
description: 
tags: 
cover_image: https://images.unsplash.com/photo-1520589884715-55ac7417f2ee?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1740&q=80
canonical_url: https://vanslaars.io/articles/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](https://codesandbox.io/s/sandpack-project-5iz0cg?fontsize=14&hidenavigation=1&theme=dark). 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](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/sandpack-project-5iz0cg?fontsize=14&hidenavigation=1&theme=dark)

You can follow along with [live examples embedded right in the article at the original source](https://vanslaars.io/articles/create-a-card-flip-animation-with-css).

## Initial Setup

Our card will consist of a pair of `div`s, 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 `div`s and initial CSS to get started.

### Markup

```html
<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>
```


### 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.

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


#### Card Wrapper

Looking at the markup, we can see that this div wraps the two `div`s 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`.

```css
.card {
  margin-top: 1rem;
  min-height: 300px;
  width: 250px;
  position: relative;
  border-radius: 0.25rem;
}
```


#### 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](https://developer.mozilla.org/en-US/docs/Web/CSS/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.

```css
.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%;
}
```

## 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`.

```html
<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>
```

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

## Transform Cards Based on The `flipped` Class

Now that we have the `flipped` class being toggled on our `div`s, 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.

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

.cardBack.flipped {
  transform: rotateY(0deg);
}
```

### 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.

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

.cardFront.flipped {
  transform: rotateY(180deg);
}
```

## 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.

```diff
.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;
}
```

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 `div`s. 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.

```diff
.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;
}
```

## 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:

```diff
.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;
}
```

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](https://developer.mozilla.org/en-US/docs/Web/CSS/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.

```diff
.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);
}
```

### 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.

```diff
.cardFront.flipped {
-  transform: perspective(600px) rotateY(180deg);
+  transform: perspective(600px) rotateY(-180deg);
}
```

## 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.

```diff
.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);
}
```

## 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](https://egghead.io/lessons/css-create-a-card-flip-animation-with-css?af=5mx0ie) a while back, check it out!