Build a Kanban Board with Vue 3 and Drag & Drop

Build a Kanban Board with Vue 3 and Drag & Drop

Build a Kanban Board with Vue 3 and Drag & Drop

Build a fully functional Kanban board with drag-and-drop between columns — a great project for mastering Vue 3 component composition and state management.

Features

  • Drag cards between columns
  • Add, edit, and delete cards and columns
  • Card labels, due dates, and assignees
  • Pinia store with localStorage persistence
  • Smooth CSS transitions for drag feedback

Step 1 — Install Dependencies

npm create vue@latest kanban-board
npm install pinia sortablejs vue-draggable-plus

Step 2 — Board Store

// stores/board.js
export const useBoardStore = defineStore('board', () => {
  const columns = ref([
    { id: 'todo',       title: 'To Do',       cards: [] },
    { id: 'inprogress', title: 'In Progress',  cards: [] },
    { id: 'done',       title: 'Done',         cards: [] },
  ])

  function addCard(columnId, { title, label = 'feature' }) {
    const col = columns.value.find(c => c.id === columnId)
    col?.cards.push({ id: crypto.randomUUID(), title, label, createdAt: Date.now() })
  }

  function moveCard(fromColId, toColId, cardId, toIndex) {
    const from = columns.value.find(c => c.id === fromColId)
    const to   = columns.value.find(c => c.id === toColId)
    const card = from?.cards.find(c => c.id === cardId)
    if (!from || !to || !card) return
    from.cards = from.cards.filter(c => c.id !== cardId)
    to.cards.splice(toIndex, 0, card)
  }

  function deleteCard(columnId, cardId) {
    const col = columns.value.find(c => c.id === columnId)
    if (col) col.cards = col.cards.filter(c => c.id !== cardId)
  }

  return { columns, addCard, moveCard, deleteCard }
}, { persist: true })

Step 3 — Draggable Column

<!-- KanbanColumn.vue -->
<script setup>
import { VueDraggable } from 'vue-draggable-plus'
import KanbanCard from './KanbanCard.vue'

const props = defineProps(['column'])
const emit  = defineEmits(['add'])
</script>

<template>
  <div class="column">
    <h3>{{ column.title }} <span>{{ column.cards.length }}</span></h3>

    <VueDraggable
      v-model="column.cards"
      group="cards"
      :animation="150"
      class="card-list"
    >
      <KanbanCard
        v-for="card in column.cards"
        :key="card.id"
        :card="card"
        :column-id="column.id"
      />
    </VueDraggable>

    <button class="add-btn" @click="emit('add', column.id)">+ Add Card</button>
  </div>
</template>
All Comments