3D Flip card tutorial using Tailwind CSS

by Matthew Rhoads,

3D Flip Cards using Tailwind CSS

What are Flip Cards?

Flip cards are interactive elements that display content on two sides - a front face and back face. When triggered, the card flips to reveal the alternate side, creating an engaging user experience.

Benefits of Flip Cards

Flip cards offer several advantages for engaging users on your website:

  • They provide an interactive element that encourages users to explore and discover more information
  • The flipping motion adds a fun, dynamic visual effect that captures attention
  • Flip cards are an efficient way to present additional content within a limited space
  • They work well for highlighting key features, services, or products

By implementing flip cards on your site, you can create a more immersive and memorable user experience. Visitors are more likely to engage with your content and spend more time exploring what you have to offer.

What we are building~

Want to see a live demo? Demo

Full Code and Repo Github

In this guide we'll cover: how to use Tailwind's utility classes to style the flip card, including 3D transforms, perspective, and transitions.

We'll start by setting up a new Next.js component for our flip card. Then we'll apply Tailwind classes to style the card faces, handle the flipping animation, and make the cards responsive.

Let's dive in and see how it's done!

If we haven't already let's go ahead and spin up a new Next.js site site using the command npx create-next-app@latest and selecting Tailwind CSS from the setup instructions.

Next we will install the HeroIcons package

 npm i @heroicons/react

We will then need to create a new folder called 'components'.

Inside of our new components folder we will create a new file

FlipCardComponent.tsx

1. Set Up the Section Wrapper

Begin by setting up a basic section element that will wrap the entire component. This section will add padding around the content to ensure it’s well-spaced on the page.

const FlipCardComponent = () => {
  return (
    <section className="py-16 mx-auto sm:py-20">
      {/* Content will go here */}
    </section>
  );
};

export default FlipCardComponent;
  • py-16 sm:py-20: Adds vertical padding (4rem by default and 5rem on small screens and above), ensuring the section is not cramped against other content.
  • mx-auto: Centers the section horizontally on the page.

2. Add a Container for Centering Content

Next, add a container inside the section. This container will center the content and adjust its width based on the screen size.

const FlipCardComponent = () => {
  return (
    <section className="py-16 mx-auto sm:py-20">
      <div className="mx-auto flex justify-center object-center px-4 py-16 sm:py-24 lg:max-w-7xl">
        {/* Content will go here */}
      </div>
    </section>
  );
};

export default FlipCardComponent;
  • mx-auto: Centers the container within the section.
  • flex justify-center object-center px-4 py-16 sm:py-24 lg:max-w-7xl: Creates a responsive container with padding, and adjusts its maximum width on large screens to 7xl (80rem).

3. Introduce a Flexbox Wrapper for Vertical Layout

Add a Flexbox wrapper to stack the heading and the grid of service cards vertically.

This wrapper will also control the spacing between these elements.

const FlipCardComponent = () => {
  return (
    <section className="py-16 mx-auto sm:py-20">
      <div className="mx-auto flex justify-center object-center px-4 py-16 sm:py-24 lg:max-w-7xl">
        <div className="flex justify-center object-center flex-col gap-12 sm:gap-16">
          {/* Heading and grid will go here */}
        </div>
      </div>
    </section>
  );
};

export default FlipCardComponent;
  • flex justify-center object-center flex-col gap-12 sm:gap-16: Uses Flexbox to stack child elements vertically with a gap of 3rem (4rem on small screens and above).

4. Add the Heading

Let’s add the heading inside the Flexbox wrapper. This heading is styled to adjust its size and color based on screen size.

const FlipCardComponent = () => {
  return (
    <section className="py-16 mx-auto sm:py-20">
      <div className="mx-auto flex justify-center object-center px-4 py-16 sm:py-24 lg:max-w-7xl">
        <div className="flex justify-center object-center flex-col gap-12 sm:gap-16">
          <h2 className="text-4xl font-semibold tracking-tight text-gray-950 sm:text-5xl lg:text-6xl">
            Services
          </h2>
          {/* Grid will go here */}
        </div>
      </div>
    </section>
  );
};

export default FlipCardComponent;
  • text-4xl sm:text-5xl lg:text-6xl: Adjusts the font size responsively, from 2.25rem to 3.75rem.
  • font-semibold: Adds a medium-bold weight to make the heading stand out.
  • tracking-tight: Slightly reduces letter spacing for a more cohesive look.
  • text-gray-950: Applies a deep gray color to the text, ensuring high contrast and readability.

5. Create the Grid Layout for Service Cards

Now, we’ll create a grid layout to hold the service cards.

This grid will have responsive behavior, adjusting from a single column to three columns on larger screens.

const FlipCardComponent = () => {
  return (
    <section className="py-16 mx-auto sm:py-20">
      <div className="mx-auto flex justify-center object-center px-4 py-16 sm:py-24 lg:max-w-7xl">
        <div className="flex justify-center object-center flex-col gap-12 sm:gap-16">
          <h2 className="text-4xl font-semibold tracking-tight text-gray-950 sm:text-5xl lg:text-6xl">
            Services
          </h2>
          <div className="mx-auto grid gap-12 space-y-10 md:space-y-0 sm:gap-16 lg:grid-cols-3">
            {/* Service cards will go here */}
          </div>
        </div>
      </div>
    </section>
  );
};

export default FlipCardComponent;
  • grid gap-12 space-y-10 md:space-y-0 sm:gap-16 lg:grid-cols-3: Sets up a responsive grid. The gap-12 and sm:gap-16 add spacing between grid items. space-y-10 removes space between rows on larger screens (md:space-y-0). On large screens (lg:), the grid switches to three columns.

6. Build the Flip Card Container

We’ll start building the individual service cards, with a container that handles the 3D flip effect.

const FlipCardComponent = () => {
  const services = [
    {
      step: "01",
      name: "Diagnostics",
      imageUrl: "https://images.unsplash.com/photo-1632733711679-529326f6db12?q=80&w=2940&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
      description: "State-of-the-art diagnostics to accurately identify vehicle issues.",
    },
    // Other services...
  ];

  return (
    <section className="py-16 mx-auto sm:py-20">
      <div className="mx-auto flex justify-center object-center px-4 py-16 sm:py-24 lg:max-w-7xl">
        <div className="flex justify-center object-center flex-col gap-12 sm:gap-16">
          <h2 className="text-4xl font-semibold tracking-tight text-gray-950 sm:text-5xl lg:text-6xl">
            Services
          </h2>
          <div className="mx-auto grid gap-12 space-y-10 md:space-y-0 sm:gap-16 lg:grid-cols-3">
            {services.map((service) => (
              <div key={service.name} className="group h-96 w-96 [perspective:1000px]">
                <div className="relative h-full w-full rounded-xl shadow-xl transition-all duration-500 [transform-style:preserve-3d] group-hover:[transform:rotateY(180deg)]">
                  {/* Front and back faces will go here */}
                </div>
              </div>
            ))}
          </div>
        </div>
      </div>
    </section>
  );
};

export default FlipCardComponent;
  • group h-96 w-96 [perspective:1000px]: Sets up the card container with a 3D perspective and a fixed size of 24rem by 24rem.

  • relative h-full w-full rounded-xl shadow-xl transition-all duration-500 [transform-style:preserve-3d] group-hover:[transform:rotateY(180deg)]:

    • relative: Positions the inner container relative to its parent for absolute positioning of child elements.
    • h-full w-full: Ensures the inner container fills the outer container completely.
    • rounded-xl: Rounds the corners of the card to create a softer, modern look.
    • shadow-xl: Applies a large shadow to give the card depth and make it stand out.
    • transition-all duration-500: Smooths the transition effects over 0.5 seconds, making the flip more fluid.
    • [transform-style:preserve-3d]: Maintains the 3D space for the card elements.
    • group-hover:[transform:rotateY(180deg)]: Rotates the card by 180 degrees on hover, creating the flip effect.

7. Add the Front Face of the Card

Let’s add the front face of the card, which will include an image and the service name.

const FlipCardComponent = () => {
  const services = [
    {
      step: "01",
      name: "Diagnostics",
      imageUrl: "https://images.unsplash.com/photo-1632733711679-529326f6db12?q=80&w=2940&auto=format&fit=crop&ixlib=rb-4.0.3 &ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
      description: "State-of-the-art diagnostics to accurately identify vehicle issues.",
    },
    // Other services...
  ];

  return (
    <section className="py-16 mx-auto sm:py-20">
      <div className="mx-auto flex justify-center object-center px-4 py-16 sm:py-24 lg:max-w-7xl">
        <div className="flex justify-center object-center flex-col gap-12 sm:gap-16">
          <h2 className="text-4xl font-semibold tracking-tight text-gray-950 sm:text-5xl lg:text-6xl">
            Services
          </h2>
          <div className="mx-auto grid gap-12 space-y-10 md:space-y-0 sm:gap-16 lg:grid-cols-3">
            {services.map((service) => (
              <div key={service.name} className="group h-96 w-96 [perspective:1000px]">
                <div className="relative h-full w-full rounded-xl shadow-xl transition-all duration-500 [transform-style:preserve-3d] group-hover:[transform:rotateY(180deg)]">
                  {/* Front Face */}
                  <div className="absolute inset-0 h-full w-full rounded-xl [backface-visibility:hidden]">
                    {service.imageUrl && (
                      <Image
                        className="object-cover cursor-pointer object-left h-full w-full rounded-xl"
                        src={service.imageUrl}
                        alt={service.name}
                        width={320}
                        height={320}
                      />
                    )}
                    <p className="md:my-6 text-2xl">{service.name}</p>
                  </div>
                  {/* Back Face will go here */}
                </div>
              </div>
            ))}
          </div>
        </div>
      </div>
    </section>
  );
};

export default FlipCardComponent;
  • absolute inset-0 h-full w-full rounded-xl: Positions the front face absolutely within the card container, ensuring it covers the entire area and aligns perfectly with the back face.
  • [backface-visibility:hidden]: Hides the front face when it’s rotated out of view during the flip.
  • object-cover cursor-pointer object-left h-full w-full rounded-xl: Ensures the image covers the entire area, has rounded corners, and is positioned to the left without distortion.
  • md:my-6 text-2xl: Adds vertical margin and sets the font size for the service name.

8. Add the Back Face of the Card

Now, we’ll add the back face, which will appear when the card is flipped. This face will include a service description and a call-to-action button.

const FlipCardComponent = () => {
  const services = [
    {
      step: "01",
      name: "Diagnostics",
      imageUrl: "https://images.unsplash.com/photo-1632733711679-529326f6db12?q=80&w=2940&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
      description: "State-of-the-art diagnostics to accurately identify vehicle issues.",
    },
    // Other services...
  ];

  return (
    <section className="py-16 mx-auto sm:py-20">
      <div className="mx-auto flex justify-center object-center px-4 py-16 sm:py-24 lg:max-w-7xl">
        <div className="flex justify-center object-center flex-col gap-12 sm:gap-16">
          <h2 className="text-4xl font-semibold tracking-tight text-gray-950 sm:text-5xl lg:text-6xl">
            Services
          </h2>
          <div className="mx-auto grid gap-12 space-y-10 md:space-y-0 sm:gap-16 lg:grid-cols-3">
            {services.map((service) => (
              <div key={service.name} className="group h-96 w-96 [perspective:1000px]">
                <div className="relative h-full w-full rounded-xl shadow-xl transition-all duration-500 [transform-style:preserve-3d] group-hover:[transform:rotateY(180deg)]">
                  {/* Front Face */}
                  <div className="absolute inset-0 h-full w-full rounded-xl [backface-visibility:hidden]">
                    {service.imageUrl && (
                      <Image
                        className="object-cover cursor-pointer object-left h-full w-full rounded-xl"
                        src={service.imageUrl}
                        alt={service.name}
                        width={320}
                        height={320}
                      />
                    )}
                    <p className="md:my-6 text-2xl">{service.name}</p>
                  </div>
                  {/* Back Face */}
                  <div className="absolute inset-0 h-full w-full rounded-xl bg-black/80 px-12 text-center text-slate-200 [transform:rotateY(180deg)] [backface-visibility:hidden]">
                    <div className="flex min-h-full flex-col items-center justify-center">
                      <h2 className="text-2xl font-bold mb-4">{service.name}</h2>
                      <p className="text-lg text-pretty text-center mb-4">
                        {service.description}
                      </p>
                      <a href="tel:555-555-5555" className="inline-flex">
                        <button className="my-2 bg-yellow-800 hover:bg-yellow-700 text-white font-bold py-2 px-4 w-auto rounded-full inline-flex items-center">
                          <span>Schedule Service</span>
                          <WrenchScrewdriverIcon className="h-6 w-6 ml-2" />
                        </button>
                      </a>
                    </div>
                  </div>
                </div>
              </div>
            ))}
          </div>
        </div>
      </div>
    </section>
  );
};

export default FlipCardComponent;
  • absolute inset-0 h-full w-full rounded-xl: Same positioning and sizing as the front face to ensure they align perfectly when the card flips.
  • bg-black/80 px-12 text-center text-slate-200: Applies a semi-transparent black background (80% opacity), adds padding, centers the text, and styles it with a slate color.
  • [transform:rotateY(180deg)]: Pre-rotates the back face by 180 degrees, so it’s hidden until the card is flipped.
  • flex min-h-full flex-col items-center justify-center: Creates a flexbox container that vertically centers the content, ensuring the text and button are always centered within the card.
  • Button Classes:
    • my-2 bg-yellow-800 hover:bg-yellow-700 text-white font-bold py-2 px-4 w-auto rounded-full inline-flex items-center: Styles the button with appropriate padding, font size, and hover/focus states to ensure it’s accessible and visually appealing.

9. Final Code

Here’s the full component after all the steps:

import React from "react";
import { WrenchScrewdriverIcon } from "@heroicons/react/20/solid";
import Image from "next/image";

const services = [
  {
    step: "01",
    name: "Diagnostics",
    imageUrl:
      "https://images.unsplash.com/photo-1632733711679-529326f6db12?q=80&w=2940&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
    description:
      "State-of-the-art diagnostics to accurately identify vehicle issues.",
  },
  {
    step: "02",
    name: "Repairs",
    imageUrl:
      "https://images.unsplash.com/photo-1687462970787-61d953508926?q=80&w=3087&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
    description:
      "Engine overhauls to brake replacements, we ensure high-quality work for your vehicle’s longevity.",
  },
  {
    step: "03",
    name: "Maintenance",
    imageUrl:
      "https://images.unsplash.com/photo-1635437536607-b8572f443763?q=80&w=2940&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
    description:
      "Oil changes, tire rotations, and more to enhance performance and prevent future issues.",
  },
];

const FlipCardComponent = () => {
  return (
    <section className="py-16 mx-auto sm:py-20">
      <div class

Name="mx-auto flex justify-center object-center px-4 py-16 sm:py-24 lg:max-w-7xl">
        <div className="flex justify-center object-center flex-col gap-12 sm:gap-16">
          <h2 className="text-4xl font-semibold tracking-tight text-gray-950 sm:text-5xl lg:text-6xl">
            Services
          </h2>
          <div className="mx-auto grid gap-12 space-y-10 md:space-y-0 sm:gap-16 lg:grid-cols-3">
            {services.map((service) => (
              <div key={service.name} className="group h-96 w-96 [perspective:1000px]">
                <div className="relative h-full w-full rounded-xl shadow-xl transition-all duration-500 [transform-style:preserve-3d] group-hover:[transform:rotateY(180deg)]">
                  {/* Front Face */}
                  <div className="absolute inset-0 h-full w-full rounded-xl [backface-visibility:hidden]">
                    {service.imageUrl && (
                      <Image
                        className="object-cover cursor-pointer object-left h-full w-full rounded-xl"
                        src={service.imageUrl}
                        alt={service.name}
                        width={320}
                        height={320}
                      />
                    )}
                    <p className="md:my-6 text-2xl">{service.name}</p>
                  </div>
                  {/* Back Face */}
                  <div className="absolute inset-0 h-full w-full rounded-xl bg-black/80 px-12 text-center text-slate-200 [transform:rotateY(180deg)] [backface-visibility:hidden]">
                    <div className="flex min-h-full flex-col items-center justify-center">
                      <h2 className="text-2xl font-bold mb-4">{service.name}</h2>
                      <p className="text-lg text-pretty text-center mb-4">
                        {service.description}
                      </p>
                      <a href="tel:555-555-5555" className="inline-flex">
                        <button className="my-2 bg-yellow-800 hover:bg-yellow-700 text-white font-bold py-2 px-4 w-auto rounded-full inline-flex items-center">
                          <span>Schedule Service</span>
                          <WrenchScrewdriverIcon className="h-6 w-6 ml-2" />
                        </button>
                      </a>
                    </div>
                  </div>
                </div>
              </div>
            ))}
          </div>
        </div>
      </div>
    </section>
  );
};

export default FlipCardComponent;

Recap

By adding interactive elements to your website you can increase user engagement while also providing valuable information to users in an easy to consume manner.

Cheers 🚀

More articles

How To: Interactive Pricing Component with Tailwind CSS

Step by step guide to create an interactive pricing component using Tailwind CSS

Read more

Missoula-Based Web Development and design

Learn more about working with a Missoula based web development and design agency

Read more

Tell us about your project

Our offices

  • Missoula
    225 W Front St,
    Missoula, MT 59802
  • Helena
    317 Cruse Ave Suite 202,
    Helena, MT 59601
  • Bozeman
    544 E Main St Unit B,
    Bozeman, MT 59715