Docs
Swipe Cards

Swipe Cards

A dynamic card swiping component with customizable effects and animations, perfect for creating interactive user interfaces like dating apps or product showcases.

Sarah's profile

Sarah

28

New York City

Adventure seeker and coffee enthusiast. Love exploring new places and trying different cuisines.

TravelPhotographyCookingHiking
Alex's profile

Alex

31

San Francisco

Tech entrepreneur by day, musician by night. Looking for someone to share adventures with.

MusicTechnologyStartupsFitness
Emily's profile

Emily

26

London

Art curator with a passion for contemporary design. Always up for gallery visits and creative discussions.

ArtDesignMuseumsWine Tasting
Michael's profile

Michael

29

Los Angeles

Film director seeking inspiration. Let's create something beautiful together.

CinemaPhotographyTravelBooks
Sophie's profile

Sophie

27

Paris

Pastry chef with a sweet tooth. Looking for someone to share my culinary experiments with.

BakingFoodTravelLanguages
Julia's profile

Julia

25

Berlin

Graphic designer with a passion for typography. Always looking for new design challenges.

DesignTypographyMusicCycling

Installation

Copy and paste the following code into your project.

components/ui/swipe-cards.tsx

"use client";
 
import * as React from "react";
import { motion, useMotionValue, useTransform } from "framer-motion";
import { cn } from "@/lib/utils";
import Image from "next/image";
 
export interface CardData {
  id: number;
  url: string;
  name?: string;
  age?: number;
  location?: string;
  bio?: string;
  interests?: string[];
}
 
export interface SwipeCardsProps extends React.HTMLAttributes<HTMLDivElement> {
  data: CardData[];
  onSwipe?: (id: number, direction: "left" | "right") => void;
  className?: string;
}
 
export interface CardProps extends CardData {
  cards: CardData[];
  setCards: React.Dispatch<React.SetStateAction<CardData[]>>;
  onSwipe?: (id: number, direction: "left" | "right") => void;
  className?: string;
}
 
export function SwipeCards({
  data,
  onSwipe,
  className,
  ...props
}: SwipeCardsProps) {
  const [cards, setCards] = React.useState<CardData[]>(data);
 
  return (
    <div
      className={cn(
        "relative grid h-full w-full place-items-center",
        className,
      )}
      {...props}
    >
      {cards.map((card) => (
        <Card
          key={card.id}
          cards={cards}
          setCards={setCards}
          onSwipe={onSwipe}
          {...card}
        />
      ))}
      {cards.length === 0 && (
        <div className="text-muted-foreground">No more profiles</div>
      )}
    </div>
  );
}
 
function Card({
  id,
  url,
  name,
  age,
  location,
  bio,
  interests,
  setCards,
  cards,
  onSwipe,
  className,
}: CardProps) {
  const x = useMotionValue(0);
  const rotateRaw = useTransform(x, [-150, 150], [-18, 18]);
  const opacity = useTransform(x, [-150, 0, 150], [0, 1, 0]);
  const isFront = id === cards[cards.length - 1].id;
 
  const rotate = useTransform(() => {
    const offset = isFront ? 0 : id % 2 ? 6 : -6;
    return `${rotateRaw.get() + offset}deg`;
  });
 
  const handleDragEnd = () => {
    const xVal = x.get();
    if (Math.abs(xVal) > 100) {
      setCards((pv) => pv.filter((v) => v.id !== id));
      onSwipe?.(id, xVal > 0 ? "right" : "left");
    }
  };
 
  const [imageError, setImageError] = React.useState(false);
 
  return (
    <motion.div
      className={cn(
        "relative h-96 w-72 origin-bottom select-none rounded-lg shadow-lg",
        isFront && "cursor-grab active:cursor-grabbing",
        className,
      )}
      style={{
        gridRow: 1,
        gridColumn: 1,
        x,
        opacity,
        rotate,
        transition: "0.125s transform",
        touchAction: "none",
        boxShadow: isFront
          ? "0 20px 25px -5px rgb(0 0 0 / 0.5), 0 8px 10px -6px rgb(0 0 0 / 0.5)"
          : undefined,
      }}
      animate={{
        scale: isFront ? 1 : 0.98,
      }}
      drag={isFront ? "x" : false}
      dragConstraints={{ left: -1000, right: 1000 }}
      dragElastic={0.15}
      dragSnapToOrigin
      onDragEnd={handleDragEnd}
      whileTap={{ scale: 0.98 }}
      whileDrag={{ scale: 1.02 }}
    >
      {imageError ? (
        <div className="flex h-full w-full items-center justify-center rounded-lg bg-muted text-muted-foreground">
          Failed to load image
        </div>
      ) : (
        <div className="relative h-full w-full overflow-hidden rounded-lg">
          <Image
            src={url}
            alt={`${name}'s profile`}
            fill
            sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
            className="pointer-events-none object-cover"
            onError={() => setImageError(true)}
            priority={isFront}
            quality={75}
          />
          <div className="absolute inset-x-0 bottom-0 bg-linear-to-t from-black/80 to-transparent p-4 text-white">
            <div className="flex items-center gap-2">
              {name && <h3 className="text-xl font-semibold">{name}</h3>}
              {age && <span className="text-lg">{age}</span>}
            </div>
            {location && <p className="text-sm text-gray-200">{location}</p>}
            {bio && <p className="mt-2 line-clamp-2 text-sm">{bio}</p>}
            {interests && interests.length > 0 && (
              <div className="mt-2 flex flex-wrap gap-1">
                {interests.map((interest) => (
                  <span
                    key={interest}
                    className="rounded-full bg-white/20 px-2 py-0.5 text-xs"
                  >
                    {interest}
                  </span>
                ))}
              </div>
            )}
          </div>
        </div>
      )}
    </motion.div>
  );
}

Update the import paths to match your project setup.

import { SwipeCards } from "@/components/ui/swipe-cards";

Usage

import { SwipeCards } from "@/components/ui/swipe-cards";
 
const cardData = [
  {
    id: 1,
    name: "Sarah",
    age: 28,
    location: "New York City",
    bio: "Adventure seeker and coffee enthusiast.",
    interests: ["Travel", "Photography", "Cooking"],
    url: "path/to/image.jpg",
  },
  // ... more cards
];
 
export default function Demo() {
  return (
    <div className="h-[500px] w-full">
      <SwipeCards
        data={cardData}
        onSwipe={(id, direction) => {
          console.log(`Card ${id} swiped ${direction}`);
        }}
      />
    </div>
  );
}

Props

SwipeCards

PropTypeDescription
dataCardData[]Array of card data to display
onSwipe(id: number, direction: "left" | "right") => voidCallback when a card is swiped
classNamestringOptional CSS class to style the container

CardData

PropertyTypeDescription
idnumberUnique identifier for the card
urlstringImage URL for the card
namestring?Optional name to display
agenumber?Optional age to display
locationstring?Optional location to display
biostring?Optional biography text
interestsstring[]?Optional array of interests

Examples

Basic Cards

<SwipeCards
  data={[
    {
      id: 1,
      url: "path/to/image.jpg",
    },
    // ... more cards
  ]}
/>

Profile Cards

<SwipeCards
  data={[
    {
      id: 1,
      name: "Alex",
      age: 28,
      location: "San Francisco",
      bio: "Tech enthusiast and coffee lover",
      interests: ["Technology", "Coffee", "Travel"],
      url: "path/to/profile.jpg",
    },
    // ... more profiles
  ]}
  onSwipe={(id, direction) => {
    // Handle swipe action
    if (direction === "right") {
      // Handle right swipe (e.g., like)
    } else {
      // Handle left swipe (e.g., pass)
    }
  }}
/>