Skip to content

Live Examples

Interactive demos showcasing different features and use cases.

🚀 Basic Usage

Simple swipeable cards with minimal setup.

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

const cards = ref([
  { text: 'Mathematics', subtitle: 'Basic algebra and geometry', color: 'from-blue-500 to-blue-600' },
  { text: 'Science', subtitle: 'Physics and chemistry fundamentals', color: 'from-green-500 to-green-600' },
  { text: 'Literature', subtitle: 'Classic novels and poetry', color: 'from-purple-500 to-purple-600' },
])
</script>

<template>
  <div class="w-full flex justify-center items-center py-20">
    <div class="max-w-sm w-full">
      <FlashCards :items="cards" #="{ item }">
        <BasicCard :item="item" />
      </FlashCards>
    </div>
  </div>
</template>
vue
<script setup lang="ts">
interface CardItem {
  text: string
  subtitle: string
  color: string
}

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

<template>
  <div class="relative overflow-hidden rounded-2xl shadow-2xl h-64 bg-white dark:bg-gray-800">
    <!-- Gradient header -->
    <div :class="`bg-gradient-to-r ${item.color} h-20 relative`">
      <div class="absolute inset-0 bg-black/10" />
      <div class="absolute bottom-3 left-6 text-white">
        <div class="w-8 h-8 bg-white/20 rounded-full flex items-center justify-center">
          <div class="w-4 h-4 bg-white rounded-full" />
        </div>
      </div>
    </div>

    <!-- Card content -->
    <div class="p-6 flex flex-col justify-center items-start h-44">
      <h2 class="text-2xl font-bold text-gray-800 dark:text-gray-100 mb-2">
        {{ item.text }}
      </h2>
      <p class="text-gray-600 dark:text-gray-300 text-sm leading-relaxed">
        {{ item.subtitle }}
      </p>

      <!-- Card footer -->
      <div class="absolute bottom-4 right-6 flex space-x-2">
        <div class="w-2 h-2 bg-gray-300 rounded-full" />
        <div class="w-2 h-2 bg-gray-300 rounded-full" />
        <div class="w-2 h-2 bg-blue-500 rounded-full" />
      </div>
    </div>
  </div>
</template>

🚧 Drag Limits

Demonstrate drag limits with checkboxes to toggle Y dragging (0px) and X dragging (200px) constraints.

Source
vue
<script lang="ts" setup>
import { ref } from 'vue'
import { FlashCards } from 'vue3-flashcards'
import LimitCard from './LimitCard.vue'
import '../assets/index.css'

const cards = ref([
  { text: 'Mathematics', subtitle: 'Basic algebra and geometry', color: 'from-blue-500 to-blue-600' },
  { text: 'Science', subtitle: 'Physics and chemistry fundamentals', color: 'from-green-500 to-green-600' },
  { text: 'Literature', subtitle: 'Classic novels and poetry', color: 'from-purple-500 to-purple-600' },
])

const limitY = ref(true)
const limitX = ref(true)
</script>

<template>
  <div class="w-full flex flex-col justify-center items-center py-20 space-y-8">
    <div class="flex space-x-6 p-4 bg-gray-100 dark:bg-gray-800 rounded-lg">
      <label class="flex items-center space-x-2">
        <input v-model="limitY" type="checkbox" class="toggle toggle-primary">
        <span class="text-gray-700 dark:text-gray-300">Limit Y (0px)</span>
      </label>
      <label class="flex items-center space-x-2">
        <input v-model="limitX" type="checkbox" class="toggle toggle-primary">
        <span class="text-gray-700 dark:text-gray-300">Limit X (200px)</span>
      </label>
    </div>

    <div class="max-w-sm w-full">
      <FlashCards
        :items="cards"
        :max-dragging-x="limitX ? 200 : null"
        :max-dragging-y="limitY ? 0 : null"
        #="{ item }"
      >
        <LimitCard :item="item" />
      </FlashCards>
    </div>
  </div>
</template>
vue
<script setup lang="ts">
interface CardItem {
  text: string
  subtitle: string
  color: string
}

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

<template>
  <div class="relative overflow-hidden rounded-2xl shadow-2xl h-64 bg-white dark:bg-gray-800 transform transition-all duration-300 hover:scale-105">
    <!-- Gradient header -->
    <div :class="`bg-gradient-to-r ${item.color} h-20 relative`">
      <div class="absolute inset-0 bg-black/10" />
      <div class="absolute bottom-3 left-6 text-white">
        <div class="w-8 h-8 bg-white/20 rounded-full flex items-center justify-center">
          <div class="w-4 h-4 bg-white rounded-full" />
        </div>
      </div>
    </div>

    <!-- Card content -->
    <div class="p-6 flex flex-col justify-center items-start h-44">
      <h2 class="text-2xl font-bold text-gray-800 dark:text-gray-100 mb-2">
        {{ item.text }}
      </h2>
      <p class="text-gray-600 dark:text-gray-300 text-sm leading-relaxed">
        {{ item.subtitle }}
      </p>

      <!-- Card footer -->
      <div class="absolute bottom-4 right-6 flex space-x-2">
        <div class="w-2 h-2 bg-gray-300 rounded-full" />
        <div class="w-2 h-2 bg-gray-300 rounded-full" />
        <div class="w-2 h-2 bg-blue-500 rounded-full" />
      </div>
    </div>
  </div>
</template>

🔄 FlipCard Integration

Two-sided cards with flip animations. Click to flip, swipe to approve or reject.

Source
vue
<script lang="ts" setup>
import { ref } from 'vue'
import { FlashCards, FlipCard } from 'vue3-flashcards'
import AnswerCard from './AnswerCard.vue'
import QuestionCard from './QuestionCard.vue'

const cards = ref([
  { text: 'What is the capital of France?', back: 'Paris', difficulty: 'Easy', category: 'Geography' },
  { text: 'What is 15 × 8?', back: '120', difficulty: 'Medium', category: 'Mathematics' },
  { text: 'Who wrote "Romeo and Juliet"?', back: 'William Shakespeare', difficulty: 'Easy', category: 'Literature' },
])

const waitAnimationEnd = ref(true)
const invertAxios = ref<false>(false)
</script>

<template>
  <div class="w-full flex flex-col justify-center items-center py-20">
    <div class="mb-8 bg-white dark:bg-gray-800 p-4 rounded-xl shadow-md">
      <label class="flex items-center gap-3 cursor-pointer mb-1">
        <input
          v-model="waitAnimationEnd"
          type="checkbox"
          class="toggle toggle-primary"
        >
        <span class="font-medium text-gray-700 dark:text-gray-300">Wait for animation end</span>
      </label>
      <label class="flex items-center gap-3 cursor-pointer">
        <input
          v-model="invertAxios"
          type="checkbox"
          class="toggle toggle-primary"
        >
        <span class="font-medium text-gray-700 dark:text-gray-300">Invert axis</span>
      </label>
    </div>

    <div class="max-w-sm w-full">
      <FlashCards :items="cards">
        <template #default="{ item }">
          <FlipCard :wait-animation-end="waitAnimationEnd" :flip-axis="invertAxios ? 'x' : 'y'">
            <template #front>
              <QuestionCard :item="item" />
            </template>
            <template #back>
              <AnswerCard :item="item" />
            </template>
          </FlipCard>
        </template>
      </FlashCards>
    </div>
  </div>
</template>
vue
<script setup lang="ts">
interface QuizItem {
  text: string
  back: string
  difficulty: string
  category: string
}

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

<template>
  <div class="relative overflow-hidden rounded-2xl shadow-2xl h-72 bg-gradient-to-br from-blue-500 to-purple-600 text-white transform transition-all duration-300">
    <!-- Question mark icon -->
    <div class="absolute top-4 right-4 w-8 h-8 bg-white/20 rounded-full flex items-center justify-center">
      <span class="text-white font-bold">?</span>
    </div>

    <!-- Category tag -->
    <div class="absolute top-4 left-4">
      <span class="px-3 py-1 bg-white/20 rounded-full text-sm font-medium">
        {{ item.category }}
      </span>
    </div>

    <!-- Question content -->
    <div class="p-6 h-full flex flex-col justify-center items-center text-center">
      <h2 class="text-lg font-bold mb-4 leading-relaxed">
        {{ item.text }}
      </h2>
      <div class="text-sm opacity-80">
        Tap to reveal answer
      </div>
    </div>

    <!-- Difficulty indicator -->
    <div class="absolute bottom-4 left-4">
      <div class="flex items-center gap-2">
        <div class="w-2 h-2 bg-white rounded-full" />
        <div :class="item.difficulty === 'Easy' ? 'w-2 h-2 bg-green-400 rounded-full' : 'w-2 h-2 bg-white/50 rounded-full'" />
        <div :class="item.difficulty === 'Medium' ? 'w-2 h-2 bg-yellow-400 rounded-full' : 'w-2 h-2 bg-white/50 rounded-full'" />
        <div :class="item.difficulty === 'Hard' ? 'w-2 h-2 bg-red-400 rounded-full' : 'w-2 h-2 bg-white/50 rounded-full'" />
      </div>
    </div>
  </div>
</template>
vue
<script setup lang="ts">
interface QuizItem {
  text: string
  back: string
  difficulty: string
  category: string
}

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

<template>
  <div class="relative overflow-hidden rounded-2xl shadow-2xl h-72 bg-gradient-to-br from-emerald-500 to-teal-600 text-white transform transition-all duration-300">
    <!-- Answer checkmark icon -->
    <div class="absolute top-4 right-4 w-8 h-8 bg-white/20 rounded-full flex items-center justify-center">
      <span class="text-white font-bold">✓</span>
    </div>

    <!-- Category tag -->
    <div class="absolute top-4 left-4">
      <span class="px-3 py-1 bg-white/20 rounded-full text-sm font-medium">
        {{ item.category }}
      </span>
    </div>

    <!-- Answer content -->
    <div class="p-6 h-full flex flex-col justify-center items-center text-center">
      <div class="text-sm opacity-80 mb-2">
        Answer:
      </div>
      <h2 class="text-2xl font-bold mb-4">
        {{ item.back }}
      </h2>
      <div class="text-sm opacity-80">
        Swipe to continue
      </div>
    </div>

    <!-- Difficulty indicator -->
    <div class="absolute bottom-4 left-4">
      <span class="px-2 py-1 bg-white/20 rounded-full text-xs">
        {{ item.difficulty }}
      </span>
    </div>
  </div>
</template>

🎮 Custom Actions

Custom action buttons with manual card controls.

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

const cards = ref([
  { id: 1, text: 'Business Strategy', description: 'Learn the fundamentals of strategic planning', icon: '💼' },
  { id: 2, text: 'Data Science', description: 'Master statistical analysis and machine learning', icon: '📊' },
  { id: 3, text: 'UI/UX Design', description: 'Create beautiful and intuitive user experiences', icon: '🎨' },
  // { id: 4, text: 'Business Strategy', description: 'Learn the fundamentals of strategic planning', icon: '💼' },
  // { id: 5, text: 'Data Science', description: 'Master statistical analysis and machine learning', icon: '📊' },
  // { id: 6, text: 'UI/UX Design', description: 'Create beautiful and intuitive user experiences', icon: '🎨' },
  // { id: 7, text: 'Business Strategy', description: 'Learn the fundamentals of strategic planning', icon: '💼' },
  // { id: 8, text: 'Data Science', description: 'Master statistical analysis and machine learning', icon: '📊' },
  // { id: 9, text: 'UI/UX Design', description: 'Create beautiful and intuitive user experiences', icon: '🎨' },
])

const disableDrag = ref(false)
</script>

<template>
  <div class="w-full flex justify-center items-center py-20">
    <div class="max-w-sm w-full">
      <div class="mb-4 flex items-center justify-center">
        <label class="flex items-center space-x-2">
          <input
            v-model="disableDrag"
            type="checkbox"
            class="toggle toggle-primary"
          >
          <span>Disable Drag</span>
        </label>
      </div>

      <FlashCards :items="cards" :disable-drag="disableDrag">
        <template #default="{ item }">
          <LearningCard :item="item" />
        </template>

        <template #actions="{ approve, reject, restore }">
          <ActionButtons
            :approve="approve"
            :reject="reject"
            :restore="restore"
          />
        </template>
      </FlashCards>
    </div>
  </div>
</template>
vue
<script setup lang="ts">
interface LearningItem {
  text: string
  description: string
  icon: string
}

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

<template>
  <div class="relative overflow-hidden rounded-2xl shadow-xl h-56 bg-white dark:bg-gray-800 transform transition-all duration-300 hover:shadow-2xl border dark:border-gray-700">
    <!-- Icon header -->
    <div class="absolute top-4 right-4 text-3xl">
      {{ item.icon }}
    </div>

    <!-- Content -->
    <div class="p-6 h-full flex flex-col justify-center">
      <h3 class="text-xl font-bold text-gray-800 dark:text-gray-100 mb-3">
        {{ item.text }}
      </h3>
      <p class="text-gray-600 dark:text-gray-300 text-sm leading-relaxed">
        {{ item.description }}
      </p>
    </div>

    <!-- Subtle decoration -->
    <div class="absolute bottom-0 left-0 w-full h-1 bg-gradient-to-r from-blue-500 to-purple-500" />
  </div>
</template>
vue
<script setup lang="ts">
defineProps<{
  approve: () => void
  reject: () => void
  restore: () => void
}>()
</script>

<template>
  <div class="grid grid-cols-2 gap-4 mt-6">
    <button
      class="px-6 py-3 bg-red-500 hover:bg-red-600 text-white rounded-xl font-medium transition-all duration-200 transform hover:scale-105 active:scale-95 shadow-lg"
      @click="reject"
    >
      ❌ Skip
    </button>
    <button
      class="px-6 py-3 bg-emerald-500 hover:bg-emerald-600 text-white rounded-xl font-medium transition-all duration-200 transform hover:scale-105 active:scale-95 shadow-lg"
      @click="approve"
    >
      ✅ Learn
    </button>
    <button
      class="col-span-2 px-6 py-3 bg-gray-500 hover:bg-gray-600 text-white rounded-xl font-medium transition-all duration-200 transform hover:scale-105 active:scale-95 shadow-lg"
      @click="restore"
    >
      ↩️ Restore Last
    </button>
  </div>
</template>

⚡ Virtual Rendering

Efficient rendering for large datasets (10,000+ cards).

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

// Generate 1000 cards for demonstration
const cards = ref(Array.from({ length: 1000 }, (_, i) => ({
  id: i + 1,
  text: `Card ${i + 1} of 1000`,
})))

const approved = ref<number[]>([])
const rejected = ref<number[]>([])

function onApprove(card: { id: number }) {
  approved.value.push(card.id)
}

function onReject(card: { id: number }) {
  rejected.value.push(card.id)
}
</script>

<template>
  <div class="w-full flex justify-center items-center min-h-[500px] p-5">
    <div class="max-w-[400px] w-full isolate">
      <FlashCards
        :items="cards"
        :virtual-buffer="2"
        #="{ item }"
        @approve="onApprove"
        @reject="onReject"
      >
        <VirtualCard
          :item="item"
          :approved="approved.length"
          :rejected="rejected.length"
          :remaining="cards.length - approved.length - rejected.length"
          :progress="((approved.length + rejected.length) / cards.length) * 100"
        />
      </FlashCards>
    </div>
  </div>
</template>
vue
<script setup lang="ts">
interface VirtualItem {
  id: number
  text: string
}

defineProps<{
  item: VirtualItem
  approved: number
  rejected: number
  remaining: number
  progress: number
}>()
</script>

<template>
  <div class="relative overflow-hidden rounded-2xl shadow-xl h-80 bg-gradient-to-br from-slate-800 to-slate-900 text-white select-none transform transition-all duration-300 hover:shadow-2xl">
    <!-- Header with number -->
    <div class="absolute top-0 left-0 w-full h-16 bg-gradient-to-r from-cyan-500 to-blue-500">
      <div class="absolute inset-0 bg-black/10" />
      <div class="absolute top-3 left-4 text-white font-bold text-lg">
        #{{ item.id }}
      </div>
      <div class="absolute top-3 right-4 text-white/80 text-sm">
        Virtual Demo
      </div>
    </div>

    <!-- Main content -->
    <div class="pt-20 p-6 h-full flex flex-col justify-center">
      <div class="card-text text-center mb-8">
        <h2 class="text-xl font-bold mb-2">
          {{ item.text }}
        </h2>
        <div class="text-cyan-400 text-sm">
          Performance test with 1000 cards
        </div>
      </div>

      <!-- Stats grid -->
      <div class="card-stats grid grid-cols-3 gap-3 text-center mb-4">
        <div class="bg-emerald-500/20 rounded-lg p-3">
          <div class="text-emerald-400 text-2xl font-bold">
            {{ approved }}
          </div>
          <div class="text-emerald-300 text-xs">
            Approved
          </div>
        </div>
        <div class="bg-red-500/20 rounded-lg p-3">
          <div class="text-red-400 text-2xl font-bold">
            {{ rejected }}
          </div>
          <div class="text-red-300 text-xs">
            Rejected
          </div>
        </div>
        <div class="bg-blue-500/20 rounded-lg p-3">
          <div class="text-blue-400 text-2xl font-bold">
            {{ remaining }}
          </div>
          <div class="text-blue-300 text-xs">
            Remaining
          </div>
        </div>
      </div>
    </div>

    <!-- Progress bar -->
    <div class="absolute bottom-0 left-0 w-full h-1 bg-slate-700">
      <div
        class="h-full bg-gradient-to-r from-cyan-500 to-blue-500 transition-all duration-300"
        :style="{ width: `${progress}%` }"
      />
    </div>
  </div>
</template>

💖 Tinder-Style Cards

Image-based cards with visual swipe indicators and smooth animations.

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',
  },
])
</script>

<template>
  <div class="max-w-[400px] mx-auto p-5">
    <div class="relative mb-5">
      <FlashCards :items="items">
        <template #default="{ item }">
          <TinderCard :item="item" />
        </template>

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

        <template #actions="{ approve, reject, restore, isEnd, canRestore }">
          <TinderActions
            :approve="approve"
            :reject="reject"
            :restore="restore"
            :is-end="isEnd"
            :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"
    :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 to-transparent"
    >
      <h2 class="m-0 mb-2 text-2xl font-semibold">
        {{ item.text }}
      </h2>
      <p class="m-0 text-base">
        {{ item.description }}
      </p>
    </div>
  </div>
</template>
vue
<script setup lang="ts">
defineProps<{
  approve: () => void
  reject: () => void
  restore: () => void
  isEnd: boolean
  canRestore: boolean
}>()
</script>

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

    </button>

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

    </button>

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

    </button>
  </div>
</template>

🎯 Delta Usage

Custom swiping indicators with dynamic opacity transitions.

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

interface WordCard {
  word: string
  translation: string
}

const cards = ref<WordCard[]>([
  { word: 'Hello', translation: 'Bonjour' },
  { word: 'World', translation: 'Monde' },
  { word: 'Thank you', translation: 'Merci' },
  { word: 'Goodbye', translation: 'Au revoir' },
  { word: 'Please', translation: 'S\'il vous plaît' },
])
</script>

<template>
  <div class="w-full flex justify-center items-center py-20">
    <div class="max-w-sm w-full">
      <FlashCards :items="cards">
        <template #default="{ item }">
          <LanguageCard :item="item" />
        </template>

        <template #approve="{ delta }">
          <SwipeOverlay :delta="Math.abs(delta)" type="approve" />
        </template>

        <template #reject="{ delta }">
          <SwipeOverlay :delta="Math.abs(delta)" type="reject" />
        </template>
      </FlashCards>
    </div>
  </div>
</template>
vue
<script setup lang="ts">
interface WordCard {
  word: string
  translation: string
}

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

<template>
  <div class="relative overflow-hidden rounded-2xl shadow-xl h-48 bg-gradient-to-br from-indigo-500 to-purple-600 text-white transform transition-all duration-300">
    <!-- Language indicator -->
    <div class="absolute top-4 left-4 px-3 py-1 bg-white/20 rounded-full text-sm font-medium">
      EN → FR
    </div>

    <!-- Book icon -->
    <div class="absolute top-4 right-4 text-white/80">
      📚
    </div>

    <!-- Content -->
    <div class="p-6 h-full flex flex-col justify-center items-center gap-3 text-center">
      <div class="text-3xl font-bold mb-2">
        {{ item.word }}
      </div>
      <div class="text-xl text-indigo-100">
        {{ item.translation }}
      </div>
    </div>

    <!-- Swipe indicators -->
    <div class="absolute bottom-4 left-1/2 transform -translate-x-1/2 flex gap-2">
      <div class="w-2 h-2 bg-white/40 rounded-full" />
      <div class="w-2 h-2 bg-white/60 rounded-full" />
      <div class="w-2 h-2 bg-white rounded-full" />
    </div>
  </div>
</template>
vue
<script setup lang="ts">
defineProps<{
  delta: number
  type: 'approve' | 'reject'
}>()
</script>

<template>
  <div
    v-if="type === 'approve'"
    class="absolute inset-0 flex items-center justify-center bg-emerald-500/90 rounded-2xl font-bold text-2xl text-white backdrop-blur-sm"
    :style="{ opacity: delta }"
  >
    <div class="text-center">
      <div class="text-4xl mb-2">

      </div>
      <div>I know this!</div>
    </div>
  </div>
  <div
    v-else
    class="absolute inset-0 flex items-center justify-center bg-red-500/90 rounded-2xl font-bold text-2xl text-white backdrop-blur-sm"
    :style="{ opacity: delta }"
  >
    <div class="text-center">
      <div class="text-4xl mb-2">
        📚
      </div>
      <div>Need to review</div>
    </div>
  </div>
</template>

📏 Scale Transform

Custom transform that scales cards instead of rotating them during swipe gestures.

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

const cards = ref([
  { id: 1, title: 'Scale Transform', description: 'This card scales instead of rotating when swiped' },
  { id: 2, title: 'Custom Animation', description: 'Demonstrates transformStyle prop override' },
  { id: 3, title: 'No Rotation', description: 'Only scaling and translation applied' },
])

// Custom transform function that scales the card instead of rotating
function scaleTransform({ delta }: DragPosition) {
  const scale = 1 - (Math.abs(delta) * 0.2)
  return `transform: scale(${scale})`
}
</script>

<template>
  <div class="w-full flex justify-center items-center py-20">
    <div class="max-w-sm w-full">
      <FlashCards
        :items="cards"
        :transform-style="scaleTransform"
      >
        <template #default="{ item }">
          <ScaleCard :card="item" />
        </template>
      </FlashCards>
    </div>
  </div>
</template>
vue
<script setup lang="ts">
interface Card {
  id: number
  title: string
  description: string
}

defineProps<{
  card: Card
}>()
</script>

<template>
  <div class="card bg-gradient-to-br from-primary to-secondary text-primary-content shadow-2xl h-80 relative overflow-hidden">
    <div class="card-body p-6">
      <div class="flex justify-between items-center mb-4">
        <h4 class="card-title text-2xl font-bold">
          {{ card.title }}
        </h4>
        <div class="badge badge-outline badge-lg">
          #{{ card.id }}
        </div>
      </div>

      <p class="text-primary-content/90 text-lg leading-relaxed flex-1">
        {{ card.description }}
      </p>

      <div class="card-actions justify-center mt-4">
        <span class="text-sm italic opacity-70">
          Swipe left or right
        </span>
      </div>
    </div>
  </div>
</template>

📚 Stack Configuration

Interactive stack controls with adjustable size (3-10 cards) and directional positioning (top, bottom, left, right).

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

const stackSize = ref(5)
const stackDirection = ref<'top' | 'bottom' | 'left' | 'right'>('bottom')

const cards = ref([
  { id: 1, title: 'Project Alpha', color: 'from-blue-500 to-cyan-500', icon: '🚀' },
  { id: 2, title: 'Design System', color: 'from-purple-500 to-pink-500', icon: '🎨' },
  { id: 3, title: 'Data Analytics', color: 'from-green-500 to-emerald-500', icon: '📊' },
  { id: 4, title: 'Machine Learning', color: 'from-orange-500 to-red-500', icon: '🤖' },
  { id: 5, title: 'Cloud Infrastructure', color: 'from-teal-500 to-blue-500', icon: '☁️' },
  { id: 6, title: 'Security Audit', color: 'from-red-500 to-pink-500', icon: '🔒' },
  { id: 7, title: 'API Integration', color: 'from-indigo-500 to-purple-500', icon: '🔗' },
  { id: 8, title: 'User Experience', color: 'from-yellow-500 to-orange-500', icon: '✨' },
])

const directions = [
  { value: 'top', label: 'Top' },
  { value: 'bottom', label: 'Bottom' },
  { value: 'left', label: 'Left' },
  { value: 'right', label: 'Right' },
]
</script>

<template>
  <div class="w-full py-16">
    <!-- Controls -->
    <div class="max-w-sm mx-auto mb-8 px-4">
      <div class="bg-white dark:bg-gray-800 rounded-xl p-4 shadow border border-gray-200 dark:border-gray-700">
        <div class="space-y-4">
          <!-- Stack Size -->
          <div>
            <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
              Stack Size: {{ stackSize }}
            </label>
            <input
              v-model.number="stackSize"
              type="range"
              min="1"
              max="10"
              step="1"
              class="w-full appearance-none h-2 rounded-lg bg-gray-200 accent-blue-500 cursor-pointer"
            >
          </div>

          <!-- Direction -->
          <div>
            <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
              Direction
            </label>
            <select
              v-model="stackDirection"
              class="w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 text-sm"
            >
              <option v-for="direction in directions" :key="direction.value" :value="direction.value">
                {{ direction.label }}
              </option>
            </select>
          </div>
        </div>
      </div>
    </div>

    <!-- FlashCards Demo -->
    <div class="flex justify-center items-center min-h-[300px]">
      <div class="max-w-xs w-full px-4">
        <FlashCards
          :items="cards"
          :stack="stackSize"
          :stack-direction="stackDirection"
          :infinite="true"
          #="{ item }"
        >
          <StackCard :item="item" />
        </FlashCards>
      </div>
    </div>
  </div>
</template>
vue
<script setup lang="ts">
interface StackItem {
  id: number
  title: string
  color: string
  icon: string
}

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

<template>
  <div class="relative overflow-hidden rounded-2xl shadow-lg h-48 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700">
    <!-- Card content -->
    <div class="p-6 flex flex-col justify-center items-center h-full text-center">
      <div :class="`w-16 h-16 rounded-2xl bg-gradient-to-r ${item.color} flex items-center justify-center shadow-md mb-4`">
        <span class="text-3xl">{{ item.icon }}</span>
      </div>

      <h3 class="text-xl font-bold text-gray-900 dark:text-gray-100 mb-2">
        {{ item.title }}
      </h3>

      <div class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
        Card #{{ item.id }}
      </div>
    </div>
  </div>
</template>

♾️ Infinite Swiping

Endless card swiping with only 3 cards that loop infinitely.

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

const cards = ref([
  { id: 1, text: 'A', color: 'from-red-500 to-red-600' },
  { id: 2, text: 'B', color: 'from-green-500 to-green-600' },
  { id: 3, text: 'C', color: 'from-blue-500 to-blue-600' },
])
</script>

<template>
  <div class="w-full flex justify-center items-center py-12">
    <div class="w-80 h-80">
      <FlashCards :items="cards" infinite #="{ item }">
        <InfiniteCard :item="item" />
      </FlashCards>
    </div>
  </div>
</template>
vue
<script setup lang="ts">
interface CardItem {
  text: string
  color: string
}

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

<template>
  <div class="relative overflow-hidden rounded-2xl shadow-lg w-72 h-72 transform transition-all duration-300 hover:scale-105">
    <div :class="`bg-gradient-to-br ${item.color} h-full relative flex items-center justify-center`">
      <div class="absolute top-2 right-2 text-white/80">
        <div class="text-xs">

        </div>
      </div>
      <div class="text-white text-lg font-bold">
        {{ item.text }}
      </div>
    </div>
  </div>
</template>

🎭 Transition Effects

Custom transition animations with fast rotating, scaling, 3D flips, and elastic bounce effects.

Source
vue
<script setup>
import { ref } from 'vue'
import FlashCards from '../../src/FlashCards.vue'
import TransitionCard from './TransitionCard.vue'

const selectedTransition = ref('fast-rotate')

const cards = [
  { id: 1, title: 'Fast Rotate', description: 'Cards spin rapidly while exiting', color: '#FF6B6B' },
  { id: 2, title: 'Scale to Zero', description: 'Cards shrink to nothing', color: '#4ECDC4' },
  { id: 3, title: '3D Flip', description: 'Cards flip in 3D space', color: '#45B7D1' },
  { id: 4, title: 'Elastic Bounce', description: 'Cards bounce with elastic effect', color: '#FFA726' },
  { id: 5, title: 'Custom Effects', description: 'Combine multiple animations', color: '#AB47BC' },
]
</script>

<template>
  <div class="max-w-lg mx-auto p-6 transition-effects-example">
    <div class="text-center mb-8">
      <h2 class="text-3xl font-bold text-base-content mb-2">
        Custom Transition Effects
      </h2>
      <p class="text-base-content/70">
        Experiment with different transition styles
      </p>
    </div>

    <div class="bg-base-200 p-6 rounded-2xl mb-8">
      <div class="grid grid-cols-2 gap-4">
        <label class="label cursor-pointer">
          <input
            v-model="selectedTransition"
            type="radio"
            value="fast-rotate"
            class="radio radio-primary"
          >
          <span class="label-text ml-2">Fast Rotate</span>
        </label>
        <label class="label cursor-pointer">
          <input
            v-model="selectedTransition"
            type="radio"
            value="scale-out"
            class="radio radio-secondary"
          >
          <span class="label-text ml-2">Scale to Zero</span>
        </label>
        <label class="label cursor-pointer">
          <input
            v-model="selectedTransition"
            type="radio"
            value="flip-3d"
            class="radio radio-accent"
          >
          <span class="label-text ml-2">3D Flip</span>
        </label>
        <label class="label cursor-pointer">
          <input
            v-model="selectedTransition"
            type="radio"
            value="elastic-bounce"
            class="radio radio-info"
          >
          <span class="label-text ml-2">Elastic Bounce</span>
        </label>
      </div>
    </div>

    <div class="h-96 w-full relative">
      <FlashCards
        :items="cards"
        :class="selectedTransition"
        infinite
      >
        <template #default="{ item }">
          <TransitionCard :card="item" />
        </template>
      </FlashCards>
    </div>
  </div>
</template>

<style>
/* Fast Rotate Animation */
.fast-rotate .flash-card__animation-wrapper--approve { animation: fast-rotate-approve 0.4s linear forwards !important; transform-origin: 50% 50% !important; }
.fast-rotate .flash-card__animation-wrapper--reject { animation: fast-rotate-reject 0.4s linear forwards !important; transform-origin: 50% 50% !important; }
.fast-rotate .flash-card__animation-wrapper--restore-approve { animation: fast-rotate-restore-approve 0.4s linear forwards !important; transform-origin: 50% 50% !important; }
.fast-rotate .flash-card__animation-wrapper--restore-reject { animation: fast-rotate-restore-reject 0.4s linear forwards !important; transform-origin: 50% 50% !important; }

@keyframes fast-rotate-approve { 0%{opacity:1;} 100%{transform:translateX(300px) rotate(360deg);opacity:0;} }
@keyframes fast-rotate-reject { 0%{opacity:1;} 100%{transform:translateX(-300px) rotate(-360deg);opacity:0;} }
@keyframes fast-rotate-restore-approve { 0%{transform:translateX(300px) rotate(360deg);opacity:0;} 100%{transform:translateX(0) rotate(0deg);opacity:1;} }
@keyframes fast-rotate-restore-reject { 0%{transform:translateX(-300px) rotate(-360deg);opacity:0;} 100%{transform:translateX(0) rotate(0deg);opacity:1;} }

/* Scale Out Animation */
.scale-out .flash-card__animation-wrapper--approve { animation: scale-out-approve 0.3s cubic-bezier(0.25,0.46,0.45,0.94) forwards !important; }
.scale-out .flash-card__animation-wrapper--reject { animation: scale-out-reject 0.3s cubic-bezier(0.25,0.46,0.45,0.94) forwards !important; }
.scale-out .flash-card__animation-wrapper--restore-approve { animation: scale-out-restore-approve 0.3s cubic-bezier(0.25,0.46,0.45,0.94) forwards !important; }
.scale-out .flash-card__animation-wrapper--restore-reject { animation: scale-out-restore-reject 0.3s cubic-bezier(0.25,0.46,0.45,0.94) forwards !important; }

@keyframes scale-out-approve { 0%{opacity:1;} 100%{transform:translateX(300px) scale(0);opacity:0;} }
@keyframes scale-out-reject { 0%{opacity:1;} 100%{transform:translateX(-300px) scale(0);opacity:0;} }
@keyframes scale-out-restore-approve { 0%{transform:translateX(300px) scale(0);opacity:0;} 100%{transform:translateX(0) scale(1);opacity:1;} }
@keyframes scale-out-restore-reject { 0%{transform:translateX(-300px) scale(0);opacity:0;} 100%{transform:translateX(0) scale(1);opacity:1;} }

/* 3D Flip Animation */
.flip-3d .flash-card__animation-wrapper--approve { animation: flip-3d-approve 0.5s ease-in-out forwards !important; }
.flip-3d .flash-card__animation-wrapper--reject { animation: flip-3d-reject 0.5s ease-in-out forwards !important; }
.flip-3d .flash-card__animation-wrapper--restore-approve { animation: flip-3d-restore-approve 0.5s ease-in-out forwards !important; }
.flip-3d .flash-card__animation-wrapper--restore-reject { animation: flip-3d-restore-reject 0.5s ease-in-out forwards !important; }

@keyframes flip-3d-approve { 0%{opacity:1;} 100%{transform:translateX(300px) rotateY(180deg) rotateX(45deg);opacity:0;} }
@keyframes flip-3d-reject { 0%{opacity:1;} 100%{transform:translateX(-300px) rotateY(-180deg) rotateX(45deg);opacity:0;} }
@keyframes flip-3d-restore-approve { 0%{transform:translateX(300px) rotateY(180deg) rotateX(45deg);opacity:0;} 100%{transform:translateX(0) rotateY(0deg) rotateX(0deg);opacity:1;} }
@keyframes flip-3d-restore-reject { 0%{transform:translateX(-300px) rotateY(-180deg) rotateX(45deg);opacity:0;} 100%{transform:translateX(0) rotateY(0deg) rotateX(0deg);opacity:1;} }

/* Elastic Bounce Animation */
.elastic-bounce .flash-card__animation-wrapper--approve { animation: elastic-bounce-approve 0.6s cubic-bezier(0.68,-0.55,0.265,1.55) forwards !important; }
.elastic-bounce .flash-card__animation-wrapper--reject { animation: elastic-bounce-reject 0.4s cubic-bezier(0.55,0.055,0.675,0.19) forwards !important; }
.elastic-bounce .flash-card__animation-wrapper--restore-approve { animation: elastic-bounce-restore-approve 0.6s cubic-bezier(0.68,-0.55,0.265,1.55) forwards !important; }
.elastic-bounce .flash-card__animation-wrapper--restore-reject { animation: elastic-bounce-restore-reject 0.4s cubic-bezier(0.55,0.055,0.675,0.19) forwards !important; }

@keyframes elastic-bounce-approve { 0%{opacity:1;} 100%{transform:translateX(300px) scale(1.3) rotate(15deg);opacity:0;} }
@keyframes elastic-bounce-reject { 0%{opacity:1;} 100%{transform:translateX(-300px) scale(1.3) rotate(-15deg);opacity:0;} }
@keyframes elastic-bounce-restore-approve { 0%{transform:translateX(300px) scale(1.3) rotate(15deg);opacity:0;} 100%{transform:translateX(0) scale(1) rotate(0deg);opacity:1;} }
@keyframes elastic-bounce-restore-reject { 0%{transform:translateX(-300px) scale(1.3) rotate(-15deg);opacity:0;} 100%{transform:translateX(0) scale(1) rotate(0deg);opacity:1;} }
</style>
vue
<script setup>
defineProps({
  card: {
    type: Object,
    required: true,
  },
})
</script>

<template>
  <div
    class="card size-full text-white shadow-xl"
    :style="{ backgroundColor: card.color }"
  >
    <div class="card-body">
      <h2 class="card-title">
        {{ card.title }}
      </h2>
      <p>{{ card.description }}</p>
      <div class="card-actions justify-end">
        <div class="badge badge-outline">
          #{{ card.id }}
        </div>
      </div>
    </div>
  </div>
</template>

Released under the MIT License.