Skip to content

Examples

Basic usage

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

const cards = ref([
  { text: 'Front 1' },
  { text: 'Front 2' },
  { text: 'Front 3' },
])
</script>

<template>
  <div class="w-full flex justify-center items-center py-20">
    <div class="max-w-sm w-full">
      <FlashCards :items="cards" #="{ item }">
        <div class="p-5 bg-base-200 border border-base-300 shadow-lg rounded-lg h-40 flex justify-center items-center">
          <div>{{ item.text }}</div>
        </div>
      </FlashCards>
    </div>
  </div>
</template>

With flip usage

Click on card to flip

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

const cards = ref([
  { text: 'Front 1', back: 'Back 1' },
  { text: 'Front 2', back: 'Back 2' },
  { text: 'Front 3', back: 'Back 3' },
])
</script>

<template>
  <div class="w-full flex justify-center items-center py-20">
    <div class="max-w-sm w-full">
      <FlashCards :items="cards" flip>
        <template #default="{ item }">
          <div class="p-5 bg-base-200 border border-base-300 shadow-lg rounded-lg h-40 flex justify-center items-center">
            <div>{{ item.text }}</div>
          </div>
        </template>
        <template #back="{ item }">
          <div class="p-5 bg-base-200 border border-base-300 shadow-lg rounded-lg h-40 flex justify-center items-center">
            <div>{{ item.back }}</div>
          </div>
        </template>
      </FlashCards>
    </div>
  </div>
</template>

With custom actions usage

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

const cards = ref([
  { text: 'Front 1' },
  { text: 'Front 2' },
  { text: 'Front 3' },
])
</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 }">
          <div class="p-5 bg-base-200 border border-base-300 shadow-lg rounded-lg h-40 flex justify-center items-center">
            <div>{{ item.text }}</div>
          </div>
        </template>

        <template #actions="{ approve, reject, restore }">
          <div class="grid grid-cols-2 gap-5 mt-5">
            <div class="btn btn-error" @click="reject">
              Reject
            </div>
            <div class="btn btn-primary" @click="approve">
              Approve
            </div>
            <div class="col-span-2 btn" @click="restore">
              Restore
            </div>
          </div>
        </template>
      </FlashCards>
    </div>
  </div>
</template>

Virtual rendering with many cards

Efficiently handles large datasets by only rendering visible cards

Source
vue
<script setup lang="ts">
import { ref } from 'vue'
import { FlashCards } from '../src'

// 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="example-container">
    <div class="cards-container">
      <FlashCards
        :items="cards"
        :virtual-buffer="1"
        #="{ item }"
        @approve="onApprove"
        @reject="onReject"
      >
        <div class="p-5 bg-base-200 border select-none border-base-300 shadow-lg rounded-lg h-40 flex flex-col gap-10 justify-center items-center">
          <div class="card-text">
            {{ item.text }}
          </div>
          <div class="card-stats">
            <div>Approved: {{ approved.length }}</div>
            <div>Rejected: {{ rejected.length }}</div>
            <div>Remaining: {{ cards.length - approved.length - rejected.length }}</div>
          </div>
        </div>
      </FlashCards>
    </div>
  </div>
</template>

<style scoped>
.example-container {
  width: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 400px;
}

.cards-container {
  max-width: 400px;
  width: 100%;
}

.card-content {
  padding: 20px;
  background-color: white;
  color: black;
  user-select: none;
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
  height: 200px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}

.card-text {
  font-size: 1.5rem;
  text-align: center;
  margin-bottom: 1rem;
}

.card-stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
  font-size: 0.875rem;
  color: #64748b;
  text-align: center;
}
</style>

Tinder-like cards

Swipeable cards with beautiful images and Tinder-like interactions. See how to use extra state for actions

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

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="tinder-container">
    <div class="tinder-cards">
      <FlashCards
        :items="items"
      >
        <template #default="{ item }">
          <div class="tinder-card" :style="{ backgroundImage: `url(${item.image})` }">
            <div class="card-content">
              <h2>{{ item.text }}</h2>
              <p>{{ item.description }}</p>
            </div>
          </div>
        </template>

        <template #empty>
          <div class="tinder-empty">
            No more cards!
          </div>
        </template>

        <template #actions="{ approve, reject, restore, isEnd, canRestore }">
          <div class="tinder-buttons">
            <button class="return-button" :disabled="!canRestore" @click="restore">

            </button>
            <button class="dislike-button" :disabled="isEnd" @click="reject">

            </button>
            <button class="like-button" :disabled="isEnd" @click="approve">

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

<style scoped>
.tinder-container {
  max-width: 400px;
  margin: 0 auto;
  padding: 20px;
}

.tinder-cards {
  position: relative;
  margin-bottom: 20px;
}

.tinder-card {
  width: 100%;
  height: 500px;
  background-size: cover;
  background-position: center;
  border-radius: 10px;
  position: relative;
  overflow: hidden;
}

.card-content {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  padding: 20px;
  background: linear-gradient(to top, rgba(0,0,0,0.8), transparent);
  color: white;
}

.card-content h2 {
  margin: 0 0 10px;
  font-size: 24px;
}

.card-content p {
  margin: 0;
  font-size: 16px;
}

.tinder-buttons {
  display: flex;
  justify-content: center;
  gap: 20px;
  margin-top: 20px;
}

.tinder-buttons button {
  width: 60px;
  height: 60px;
  border-radius: 50%;
  border: none;
  font-size: 24px;
  cursor: pointer;
  transition: transform 0.2s, opacity 0.2s;
}

.tinder-buttons button:disabled {
  cursor: not-allowed;
  opacity: 0.5;
  transform: none;
}

.tinder-buttons button:not(:disabled):hover {
  transform: scale(1.1);
}

.like-button {
  background-color: #4CAF50;
  color: white;
}

.dislike-button {
  background-color: #f44336;
  color: white;
}

.return-button {
  background-color: #2196F3;
  color: white;
}

.no-more-cards {
  text-align: center;
  font-size: 20px;
  color: #666;
  padding: 40px;
}

.swipe-left {
  animation: swipeLeft 0.3s ease-out forwards;
}

.swipe-right {
  animation: swipeRight 0.3s ease-out forwards;
}

@keyframes swipeLeft {
  to {
    transform: translateX(-200%) rotate(-20deg);
    opacity: 0;
  }
}

@keyframes swipeRight {
  to {
    transform: translateX(200%) rotate(20deg);
    opacity: 0;
  }
}
</style>

Word study with delta feedback

Language learning cards with visual feedback for known and unknown words. Shows how to use delta prop for smooth opacity transitions with custom approje/reject state.

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

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 }">
          <div
            class="p-5 bg-base-200 border border-base-300 shadow-lg rounded-lg h-40 flex flex-col justify-center items-center gap-2"
          >
            <div class="text-2xl font-bold">
              {{ item.word }}
            </div>
            <div class="text-lg text-gray-600">
              {{ item.translation }}
            </div>
          </div>
        </template>

        <template #approve="{ delta }">
          <div
            class="absolute bg-base-200 inset-0 flex items-center justify-center text-green-500 rounded-lg  font-bold text-xl"
            :style="{ opacity: delta }"
          >
            I know this!
          </div>
        </template>

        <template #reject="{ delta }">
          <div
            class="absolute bg-base-200 inset-0 flex items-center justify-center text-red-500 rounded-lg font-bold text-xl"
            :style="{ opacity: delta }"
          >
            Need to review
          </div>
        </template>
      </FlashCards>
    </div>
  </div>
</template>

⚠️ Development Notice: This package is currently in development. The API may change between minor versions until v1.0.0 is released.