I Built a Zero-Backend Kanban Board with React + TypeScript — Here's What I Learned
No server. No database. No login. Just pure React — and it actually works great.
Why I Built KanbanFlow
Every Kanban tool I used asked me to sign up, connect to a server, or pay for features I didn't need. I wanted something that just opened and worked — instantly, privately, offline.
So I built KanbanFlow — a fully-featured Kanban board that lives entirely in your browser. No account. No backend. No setup.
What It Does
- Unlimited columns and tasks — fully customizable
- Drag & drop task movement across columns
- Priority tagging — High, Medium, Low, None
- Due dates with overdue highlighting
- Live search + priority filtering
- Dark / Light theme toggle
- Auto-saves to localStorage — data persists across sessions
Tech Stack
React 18 + Vite 5 + TypeScript 5 + Tailwind CSS + React Router v6
No Redux. No external state library. Just React's useState and useReducer — keeping it lean.
The Interesting Part — localStorage as a Database
Most people reach for Firebase or Supabase immediately. But for a personal productivity tool, localStorage is genuinely underrated.
// Auto-save board state on every change
useEffect(() => {
localStorage.setItem('kanban-board', JSON.stringify(board));
}, [board]);
// Load on mount
const [board, setBoard] = useState<Board>(() => {
const saved = localStorage.getItem('kanban-board');
return saved ? JSON.parse(saved) : defaultBoard;
});
This pattern gives you:
- Instant persistence with zero latency
- Works completely offline
- No auth, no API keys, no cost
- Data stays on the user's machine (privacy win)
The tradeoff? No cross-device sync. For a personal board, that's totally acceptable.
Drag & Drop Without a Library
Instead of reaching for react-beautiful-dnd or dnd-kit, I implemented drag and drop using the native HTML5 Drag and Drop API — which kept the bundle size tiny.
const handleDragStart = (e: DragEvent, taskId: string) => {
e.dataTransfer.setData('taskId', taskId);
};
const handleDrop = (e: DragEvent, targetColumnId: string) => {
const taskId = e.dataTransfer.getData('taskId');
moveTask(taskId, targetColumnId);
};
Project Structure
I kept the component tree flat and predictable:
src/
├── types/kanban.ts # All types + constants
├── components/kanban/
│ ├── TaskCard.tsx
│ ├── KanbanColumn.tsx
│ ├── TaskForm.tsx
│ ├── Header.tsx
│ └── Toolbar.tsx
└── pages/
└── Index.tsx # Main orchestrator
One rule I followed: no component does more than one thing. KanbanColumn renders a column. TaskCard renders a card. Index.tsx wires them together.
Try It
Live: kanbanflow.shouryaparashar.in
GitHub: github.com/im-shourya/KanbanFlow
Built by Shourya Parashar — Full Stack Developer.
If you found this useful, drop a ⭐ on GitHub — it genuinely helps!
Top comments (0)