🔄 Logo Card Flipper

A sophisticated 3D flip card component that automatically cycles through different logos or content with smooth flip animations. Features realistic perspective effects, customizable timing, and seamless transitions perfect for showcasing brands, testimonials, or rotating content.


Install Dependencies

npm i motion react-icons tailwind-merge

Code

LogoCardFlipper.tsx

import React, { ReactElement, useEffect, useRef, useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { SiAmazon, SiGithub, SiGoogle, SiMeta, SiTwitch } from "react-icons/si";
import { twMerge } from "tailwind-merge";

export const DivOrigami = () => {
  return (
    <section className="flex h-72 flex-col items-center justify-center gap-12 bg-neutral-950 px-4 py-24 md:flex-row">
      <LogoRolodex
        items={[
          <LogoItem key={1} className="bg-orange-300 text-neutral-900">
            <SiAmazon />
          </LogoItem>,
          <LogoItem key={2} className="bg-green-300 text-neutral-900">
            <SiGoogle />
          </LogoItem>,
          <LogoItem key={3} className="bg-blue-300 text-neutral-900">
            <SiMeta />
          </LogoItem>,
          <LogoItem key={4} className="bg-white text-black">
            <SiGithub />
          </LogoItem>,
          <LogoItem key={5} className="bg-purple-300 text-neutral-900">
            <SiTwitch />
          </LogoItem>,
        ]}
      />
    </section>
  );
};

const DELAY_IN_MS = 2500;
const TRANSITION_DURATION_IN_SECS = 1.5;

const LogoRolodex = ({ items }: { items: ReactElement[] }) => {
  const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
  const [index, setIndex] = useState(0);

  useEffect(() => {
    intervalRef.current = setInterval(() => {
      setIndex((pv) => pv + 1);
    }, DELAY_IN_MS);

    return () => {
      clearInterval(intervalRef.current || undefined);
    };
  }, []);

  return (
    <div
      style={{
        transform: "rotateY(-20deg)",
        transformStyle: "preserve-3d",
      }}
      className="relative z-0 h-44 w-60 shrink-0 rounded-xl border border-neutral-700 bg-neutral-800"
    >
      <AnimatePresence mode="sync">
        <motion.div
          style={{
            y: "-50%",
            x: "-50%",
            clipPath: "polygon(0 0, 100% 0, 100% 50%, 0 50%)",
            zIndex: -index,
            backfaceVisibility: "hidden",
          }}
          key={index}
          transition={{
            duration: TRANSITION_DURATION_IN_SECS,
            ease: "easeInOut",
          }}
          initial={{ rotateX: "0deg" }}
          animate={{ rotateX: "0deg" }}
          exit={{ rotateX: "-180deg" }}
          className="absolute left-1/2 top-1/2"
        >
          {items[index % items.length]}
        </motion.div>
        <motion.div
          style={{
            y: "-50%",
            x: "-50%",
            clipPath: "polygon(0 50%, 100% 50%, 100% 100%, 0 100%)",
            zIndex: index,
            backfaceVisibility: "hidden",
          }}
          key={(index + 1) * 2}
          initial={{ rotateX: "180deg" }}
          animate={{ rotateX: "0deg" }}
          exit={{ rotateX: "0deg" }}
          transition={{
            duration: TRANSITION_DURATION_IN_SECS,
            ease: "easeInOut",
          }}
          className="absolute left-1/2 top-1/2"
        >
          {items[index % items.length]}
        </motion.div>
      </AnimatePresence>

      <hr
        style={{
          transform: "translateZ(1px)",
        }}
        className="absolute left-0 right-0 top-1/2 z-[999999999] -translate-y-1/2 border-t-2 border-neutral-800"
      />
    </div>
  );
};

const LogoItem = ({
  children,
  className,
}: {
  children: React.ReactNode;
  className?: string;
}) => {
  return (
    <div
      className={twMerge(
        "grid h-36 w-52 place-content-center rounded-lg bg-neutral-700 text-6xl text-neutral-50",
        className
      )}
    >
      {children}
    </div>
  );
};

Customization

You can customize the timing and animation by modifying these constants:

const DELAY_IN_MS = 3000;           // Time between flips (3 seconds)
const TRANSITION_DURATION_IN_SECS = 2; // Duration of flip animation (2 seconds)

Usage Examples

Basic Logo Showcase:

<LogoRolodex
  items={[
    <LogoItem key={1} className="bg-red-500 text-white">
      <SiNetflix />
    </LogoItem>,
    <LogoItem key={2} className="bg-blue-500 text-white">
      <SiFacebook />
    </LogoItem>,
  ]}
/>

Custom Content:

<LogoRolodex
  items={[
    <LogoItem key={1} className="bg-gradient-to-r from-purple-400 to-pink-400">
      <span className="text-2xl font-bold">Feature 1</span>
    </LogoItem>,
    <LogoItem key={2} className="bg-gradient-to-r from-green-400 to-blue-500">
      <span className="text-2xl font-bold">Feature 2</span>
    </LogoItem>,
  ]}
/>

Props

LogoRolodex Props

PropTypeDefaultDescription
itemsReactElement[]—Array of LogoItem components to cycle through

LogoItem Props

PropTypeDefaultDescription
childrenReact.ReactNode—Content to display inside the card
classNamestring—Additional CSS classes for custom styling

Animation Details

The component uses a sophisticated clipping technique to create the flip effect:

  • Top Half: Uses clipPath: "polygon(0 0, 100% 0, 100% 50%, 0 50%)"
  • Bottom Half: Uses clipPath: "polygon(0 50%, 100% 50%, 100% 100%, 0 100%)"
  • 3D Perspective: Applied with transform: "rotateY(-20deg)" for depth
  • Flip Motion: Rotates along X-axis from 0° to -180°
Logo Card Flipper16