Skip to content

Tinder Style

Intermediate

Image-based cards with visual swipe indicators and multi-directional actions.

Demo

Source
vue
<script setup lang="ts">
import { ref } from 'vue'
import { FlashCards } from 'vue3-flashcards'
import TinderActions from './TinderActions.vue'
import TinderCard from './TinderCard.vue'

interface Card {
  id: number
  text: string
  description: string
  image: string
}

const items = ref<Card[]>([
  {
    id: 1,
    text: 'Mountain Adventure',
    description: 'Explore the peaks and valleys',
    image: 'https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?w=800&q=80',
  },
  {
    id: 2,
    text: 'Beach Paradise',
    description: 'Relax by the ocean',
    image: 'https://images.unsplash.com/photo-1507525428034-b723cf961d3e?w=800&q=80',
  },
  {
    id: 3,
    text: 'City Life',
    description: 'Urban exploration',
    image: 'https://images.unsplash.com/photo-1449824913935-59a10b8d2000?w=800&q=80',
  },
  {
    id: 4,
    text: 'Forest Retreat',
    description: 'Connect with nature',
    image: 'https://images.unsplash.com/photo-1511497584788-876760111969?w=800&q=80',
  },
])

function handleSwipe(item: Card, direction: 'left' | 'right' | 'top') {
  if (direction === 'top') {
    // Super like handling
  }
}
</script>

<template>
  <div class="max-w-[400px] mx-auto p-5">
    <div class="mb-5">
      <FlashCards
        :items="items"
        :swipe-direction="['left', 'right', 'top']"
        @swipe-left="(item) => handleSwipe(item, 'left')"
        @swipe-right="(item) => handleSwipe(item, 'right')"
        @swipe-top="(item) => handleSwipe(item, 'top')"
      >
        <template #default="{ item }">
          <TinderCard :item="item" />
        </template>

        <template #left="{ delta }">
          <div class="absolute inset-0 flex items-center justify-center z-10 pointer-events-none" :style="{ backgroundColor: `rgba(0, 0, 0, ${Math.min(Math.abs(delta), 0.5)})` }">
            <div :style="{ opacity: Math.abs(delta) }" class="border-4 border-red-500 text-red-500 px-6 py-3 rounded-lg text-5xl font-black uppercase tracking-wider shadow-lg rotate-[-12deg]">
              NOPE
            </div>
          </div>
        </template>

        <template #right="{ delta }">
          <div class="absolute inset-0 flex items-center justify-center z-10 pointer-events-none" :style="{ backgroundColor: `rgba(0, 0, 0, ${Math.min(Math.abs(delta), 0.5)})` }">
            <div :style="{ opacity: Math.abs(delta) }" class="border-4 border-green-500 text-green-500 px-6 py-3 rounded-lg text-5xl font-black uppercase tracking-wider shadow-lg rotate-[12deg]">
              LIKE
            </div>
          </div>
        </template>

        <template #top="{ delta }">
          <div class="absolute inset-0 flex items-center justify-center z-10 pointer-events-none" :style="{ backgroundColor: `rgba(0, 0, 0, ${Math.min(Math.abs(delta), 0.5)})` }">
            <div :style="{ opacity: Math.abs(delta) }" class="border-4 border-blue-400 text-blue-400 px-4 py-2 rounded-lg text-3xl font-black uppercase tracking-wider shadow-lg">
              🌟 SUPER LIKE
            </div>
          </div>
        </template>

        <template #empty>
          <div class="text-center text-xl text-gray-600 p-10">
            🎉 No more cards! 🎉
          </div>
        </template>

        <template #actions="{ restore, swipeTop, swipeLeft, swipeRight, swipeBottom, isEnd, isStart, canRestore }">
          <TinderActions
            :top="swipeTop"
            :left="swipeLeft"
            :right="swipeRight"
            :bottom="swipeBottom"
            :restore="restore"
            :is-end="isEnd"
            :is-start="isStart"
            :can-restore="canRestore"
          />
        </template>
      </FlashCards>
    </div>
  </div>
</template>
vue
<script setup lang="ts">
interface TinderItem {
  id: number
  text: string
  description: string
  image: string
}

defineProps<{
  item: TinderItem
}>()
</script>

<template>
  <div
    class="w-full h-[500px] bg-cover bg-center rounded-xl relative overflow-hidden shadow-2xl border border-white/10 z-10"
    :style="{ backgroundImage: `url(${item.image})` }"
  >
    <div
      class="absolute bottom-0 left-0 right-0 p-5 text-white bg-gradient-to-t from-black/80 via-black/40 to-transparent"
    >
      <h2 class="m-0 mb-2 text-2xl font-bold drop-shadow-lg">
        {{ item.text }}
      </h2>
      <p class="m-0 text-base drop-shadow-md">
        {{ item.description }}
      </p>
    </div>

    <!-- Subtle border gradient -->
    <div class="absolute inset-0 rounded-xl border border-white/20 pointer-events-none" />
  </div>
</template>
vue
<script setup lang="ts">
defineProps<{
  // Directional methods (optional for backward compatibility)
  top?: () => void
  left?: () => void
  right?: () => void
  bottom?: () => void

  // Other methods
  restore: () => void
  skip?: () => void
  reset?: (options?: any) => void

  // State
  isEnd: boolean
  isStart: boolean
  canRestore: boolean
}>()
</script>

<template>
  <div class="flex justify-center gap-3 mt-5">
    <button
      class="w-14 h-14 rounded-full text-xl text-white bg-blue-500 transition-transform duration-200 disabled:opacity-50 disabled:cursor-not-allowed hover:scale-110"
      :disabled="!canRestore || isStart"
      @click="restore"
    >

    </button>

    <button
      class="w-14 h-14 rounded-full text-xl text-white bg-red-500 transition-transform duration-200 disabled:opacity-50 disabled:cursor-not-allowed hover:scale-110"
      :disabled="isEnd"
      @click="left"
    >

    </button>

    <!-- Super Like Button -->
    <button
      class="w-14 h-14 rounded-full text-xl text-white bg-gradient-to-br from-blue-400 to-purple-600 transition-transform duration-200 disabled:opacity-50 disabled:cursor-not-allowed hover:scale-110 shadow-lg"
      :disabled="isEnd"
      @click="top"
    >

    </button>

    <button
      class="w-14 h-14 rounded-full text-xl text-white bg-green-500 transition-transform duration-200 disabled:opacity-50 disabled:cursor-not-allowed hover:scale-110"
      :disabled="isEnd"
      @click="right"
    >

    </button>
  </div>

  <div class="text-center text-sm text-gray-600 mt-3">
    <div class="flex justify-center gap-4 text-xs">
      <span>↺ Restore</span>
      <span>✕ Nope</span>
      <span>⭐ Super Like</span>
      <span>♥ Like</span>
    </div>
    <div class="mt-2 text-xs text-gray-500">
      💡 Swipe: Left = Nope, Right = Like, Up = Super Like
    </div>
  </div>
</template>

Key Concepts

  • Multi-directional swipe: ['left', 'right', 'top']
  • Directional slots: #left, #right, #top with delta for opacity
  • Image cards: Profile-style layout with photos
  • Actions slot: Manual LIKE/NOPE/SUPER LIKE buttons

Released under the MIT License.