DEV Community

Cover image for How I built a Tinder-style Card Swipe in Next.js 16
Yoshio Nomura
Yoshio Nomura

Posted on

How I built a Tinder-style Card Swipe in Next.js 16

I recently decided to build a mobile-first roommate finder app as a way to learn the new Next.js 16 App Router.

The hardest part? Building a "Card Stack" that feels like a native app (Tinder-style) without using heavy libraries like framer-motion.

This is how I solved it using just React 19, Tailwind CSS, and good old useState.

Here is the gig!

"Moving" a stack of DOM elements was never a thing. We have been rendering one active card and changing the data behind it all this time!

To do this, we need to track two things in our state:

  1. The Index: Which item in the array are we looking at?
  2. The Direction: Is the user swiping Left (Reject) or Right (Like)?

The Code

Here is the simplified logic from my ExplorePage component:

'use client';

import { useState } from 'react';

export default function SwipeStack({ items }) {
  // 1. Track which card is active
  const [currentIndex, setCurrentIndex] = useState(0);

  // 2. Track animation direction ('left' | 'right' | null)
  const [swipeDirection, setSwipeDirection] = useState(null);

  const handleSwipe = (direction) => {
    // Step A: Trigger the animation
    setSwipeDirection(direction);

    // Step B: Wait for animation to finish, then show next card
    setTimeout(() => {
      setCurrentIndex((prev) => prev + 1);
      setSwipeDirection(null); // Reset animation
    }, 300); // Matches CSS transition duration
  };

  const currentItem = items[currentIndex];

  if (!currentItem) return <div>No more profiles!</div>;

  return (
    <div className="relative w-full max-w-sm h-[500px]">
      <div 
        className={`
          transition-all duration-300 ease-out
          ${swipeDirection === 'left' ? '-translate-x-full -rotate-12 opacity-0' : ''}
          ${swipeDirection === 'right' ? 'translate-x-full rotate-12 opacity-0' : ''}
        `}
      >
        {/* Your Card Component */}
        <Card item={currentItem} />
      </div>

      {/* Control Buttons */}
      <div className="flex gap-4 justify-center mt-8">
        <button onClick={() => handleSwipe('left')}>❌</button>
        <button onClick={() => handleSwipe('right')}>πŸ’š</button>
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Check this out, with the code, you get:

  1. Instant Feedback: When you click "Like", setSwipeDirection('right') adds the Tailwind classes translate-x-full and rotate-12. The card visually flies off the screen.

  2. State Update: The setTimeout waits exactly 300ms (the duration of our CSS transition). Once the card is off-screen, we increment currentIndex.

  3. Reset: React re-renders with the new data at the same position (center), and we remove the animation classes. To the user, it looks like a brand new card appeared behind the old one.

The Result
This creates a buttery smooth 60fps animation on mobile browsers because we are only transforming translate and opacity.

WAIT

I open-sourced the entire UI kit, including the Swipe Logic, Bottom Navigation, and Chat Interface.

You can grab the repo here to see how I handled the array filtering and mobile safe-areas:

πŸ‘‰ GitHub Repo: Next.js Mobile Marketplace Starter

πŸ‘‰ Live Demo

Let me know if you have questions about the framework!

Top comments (0)