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
Chris's profile

Chris

30

Chicago

Entrepreneur with a passion for innovation. Looking for someone to help me change the world.

StartupsInnovationTravelBooks
Laura's profile

Laura

29

Sydney

Yoga instructor with a love for nature. Always looking for new outdoor adventures.

YogaNatureTravelPhotography
Jenny's profile

Jenny

26

Tokyo

Fashion designer with a passion for style. Let's explore the city together.

FashionStyleTravelFood
Matthew's profile

Matthew

32

Melbourne

Software engineer with a love for coffee. Looking for someone to share a cuppa with.

CoffeeTechnologyStartupsTravel
Kate's profile

Kate

27

Rome

Art historian with a passion for museums. Always looking for new art to admire.

ArtHistoryMuseumsTravel
Adam's profile

Adam

31

Toronto

Entrepreneur with a passion for innovation. Looking for someone to help me change the world.

StartupsInnovationTravelBooks
Lily's profile

Lily

28

Beijing

Photographer with a love for nature. Always looking for new landscapes to capture.

PhotographyNatureTravelCooking
Lucas's profile

Lucas

29

Amsterdam

Graphic designer with a passion for illustration. Looking for someone to share my art with.

DesignIllustrationMusicCycling
Hannah's profile

Hannah

25

Copenhagen

Fashion designer with a love for style. Let's explore the city together.

FashionStyleTravelFood

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}
          />
          <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)
    }
  }}
/>