Tinder Style
IntermediateImage-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,#topwith delta for opacity - Image cards: Profile-style layout with photos
- Actions slot: Manual LIKE/NOPE/SUPER LIKE buttons