Astro JS Card Flip Component


Today, we are going to create an animated card flip component for Astro JS. This can be ported to the framework of your choice as it is using HTML, JS, and CSS. We are only using Astro to pass in the parameters and bind them to build the page.

Definition

Flip Card - This describes is a UI element which uses animation to rotate the “card” along its y axis. Like someone flipping over a playing card on a table. See the image at the top of the article for a visual.

The idea is to engage the user by having the effect triggered on a mouse hover event or something similar like a click event. This should draw the eye to that area. Then, after the flip, the user is presented with more detail. This allows you to have a cleaner UI without having to sacrifice information or make users click though to a new page.

Requirements

Before we write any code, let’s define the needs of our component.

The card flip component will,

  • take an object array that consists of 2 parameters
    • title will be displayed on the front card and the
    • items array will be listed on the back
  • flip when the user hovers (on PC), and/or click (on touch)
  • handle any number of cards (use flexbox)

Code

OK, now that we understand the goal we can start to write some code!

Input Object Model

You can put the folder wherever you want but I like to create a models folder in /src. In your models folder, create a new file called Card.ts and add the following code.

export interface Card {
    title: string;
    items: string[];
}

Here we are declaring an Interface that defines our Card. The title property is a string and will be displayed on the front card. the items property is an array of strings. These are the details about the title and will be displayed on the back of the card.

Astro Template

In the /src/components/ folder, create a new file called FlipCard.astro. Starting from the top:

The Component Script

In Astro, this is where we set up the props and such we need to make the component work.

---
import type { Card } from "../models/card";

interface Props {
  cards: Card[];
}
const { cards } = Astro.props;
---

Here we are using the Card model to define an input property. This allows us to pass in the cards array from the parent component like <FlipCard cards="{cards}" />. We’ll get to that in a moment though. First, we need to finish the component.

The Component Template

In our Astro template we define our UI. Anything between the curly braces { and } is handled by the compiler. Here, we create a template for the cards. Then use that in a loop (map) to iterate over the cards array. This is all wrapped in a container div so that we can use flexbox to allow for a dynamic layout. This addresses the requirement that we can handle any number of cards.

<div class="cards">
  {
    cards.map((card) => (
      <div class="card">
        <div class="front">{card.title}</div>
        <div class="back">
          {card.items.map((item) => (
            <div class="">{item}</div>
          ))}
        </div>
      </div>
    ))
  }
</div>

Style

Now we need to add some CSS to make the front and back occupy the same space, be the same size, and animate the flip effect.

The outer container will use flex, have it’s content centered, and the content will wrap. This allows us to have a flexible layout that will grow/shrink to handle the content.

.cards {
    display: flex;
    justify-content: center;
    flex-wrap: wrap;
}

The .card class will serve as the container for the front and back. We need to set position:relative because we will be using absolute positioning child elements (front and back) later. We also define the hover behavior. This is where we add the animation. I like adding a box shadow to really make it look like it is floating and to define the edges of the cards against the background. Feel free to edit the colors, sizes and such to fit your needs.

.card {
    perspective: 1000px;
    position: relative;
    transition: transform 0.8s;
    transform-style: preserve-3d;
    width: 400px;
    height: 400px;
    margin: 1em;
    box-shadow: var(--box-shadow);
    &:hover {
      transform: rotateY(180deg);
    }
}

Note: If you didn’t know, when you set absolute on an element, it breaks out of the normal document structure and is positioned relative to the closest ancestor that is set to relative or the containing block. We are using this to make the front and back occupy the same space. Be aware that this can cause some display issues when you are using z-index like I had.

The front and back have some things in common so let’s encapsulate that.

.front, .back {
    position: absolute;
    text-align: center;
    width: 100%;
    height: 100%;
    -webkit-backface-visibility: hidden; /* Safari */
    backface-visibility: hidden;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}

Now we get more specific. The front:

.front {
    background-color: var(--accent-dark);
    color: white;
}

And the back:

.back {
    transform: rotateY(180deg);
    background-color: white;
    color: var(--accent-dark);
    justify-content: space-around;
}

To make it pop we use different colors for the different sides.

Note: I cant get this to work in tailwind so I stuck to CSS/SCSS. It is possible but I can’t figure out the config extensions I need. I guess we could use a utility class too. If you are a Tailwind pro and can help, leave a comment below.

Usage

We did it! We now have an functioning card flip component for our Astro JS site.

We can use this anywhere you want by:

  1. Importing it.
import FlipCard from "../components/FlipCard.astro";
  1. Creating the cards array.
const cards: Card[] = [
  {
    title: "IT/Networking troubleshooting, install and repair",
    items: [
      "CompTIA A+ and N+",
      "Microsoft MCP",
      "Azure certified",
      "Windows, Linux and Mac",
    ],
  },
  {
    title: "Custom Software Development",
    items: [
      "Over 10 years of experience",
      "C#, TypeScript/JavaScript, SQL, C++",
    ],
  },
  {
    title: "3D Printing Services",
    items: ["Print and design", "Repair", "Training"],
  },
];
  1. Pass that into the component like:
<FlipCard {cards} />

or

<FlipCard cards={cards} />

And that is it! We have created a new card flip component and used it in our Astro site.

Thanks for reading. What do you think? Let me know in the comments. What would you add or change?

Issues

Mobile views

On smaller screens and touch-enabled devices there is not a hover so the hover effects are triggered on click/tap.

I tried to use IntersectionObserver to make them flip as the user scrolls but all the cards report that they are visible when they are not. I think the theme I am using in Astro is conflicting and causing an issue. I am sure I got something wrong but I haven’t figured it out yet. I would love to hear your thoughts on what it might be in the comments.

stacking contexts

I get this weird one sometimes where the card will appear over everything else. It only happens in Chrome when using dev tools device emulator. Here is a video I made showing the issue. https://youtu.be/AZy880dUDvA I have tried on a Pixel tablet and phone and can’t recreate this. At first, I thought it was an issue with the position: absolute on the back card but I set position: relative on the container and there is no z-index set on the cards at all. In fact, I only have one z-index in use and that is for the mobile menu. I would expect this to make the menu appear on top of everything else. If you have any ideas, leave a comment. This one has me confused.