A interactive whiteboard application built with React.js, Vite, and Tailwind CSS. Draw shapes, sketch freehand, customize themes, toggle dark mode, export your work, and collaborate live with others in real time — all in a polished, fluid interface.
🔗 Live Demo: https://draw.aftercp.com
This repo is the frontend only. Live collaboration is powered by a separate real-time backend:
whiteboard-backend(Node.js + Socket.IO).
- 🎨 Freehand Drawing — Smooth, canvas-based sketching experience
- 📐 Shape Tools — Draw with precision using geometric shape support
- ↩️ Per-User Undo / Redo — Undo only ever removes your own last action, even in a shared room
- 🖌️ Toolbox Controls — Customize stroke style, fill style, gap, and roughness per element
- 🤝 Live Collaboration — Go live or join a room to draw together in real time, with synced cursors and presence
- 🔗 Shareable Rooms — Share a room code or link; opening the link jumps straight into joining
- 💾 Export — Save your whiteboard as an image file via the export utility
- ⚡ Context API State Management — Decoupled board, toolbox, and collaboration state via React Context + Providers
- 📱 Responsive & Touch-Friendly — Adapts to narrow screens, and drawing works with touch/pen input, not just a mouse
| Technology | Purpose |
|---|---|
| React.js | UI library & component logic |
| Vite | Dev server & production build tool |
| Tailwind CSS | Utility-first responsive styling |
| HTML5 Canvas | Drawing surface & graphics rendering |
| Rough.js | Hand-drawn/sketchy geometric shapes |
| Perfect-Freehand | Artistic brush strokes |
| Socket.IO Client | Real-time sync with the collaboration backend |
| React Context API | Decoupled board, toolbox, and collaboration state |
The real-time backend (Socket.IO + Express) lives in a separate repo: whiteboard-backend, deployed independently on Render.
- Node.js (v16 or above)
- npm
# Clone the repository
git clone https://github.com/mankesh016/whiteboard-app.git
# Navigate to the project folder
cd whiteboard-app
# Install dependencies
npm installnpm run devOpen http://localhost:5173 in your browser.
Without a backend running, the app still works fully as a local, single-user whiteboard — only "Go live"/"Join a room" need it. To enable live collaboration locally too:
1. Clone and start the backend (in a separate folder, outside whiteboard-app):
git clone https://github.com/mankesh016/whiteboard-backend.git
cd whiteboard-backend
npm install
npm run devThis starts the Socket.IO server on http://localhost:4000.
2. Point the frontend at it — back in whiteboard-app, create a .env.development file:
echo "VITE_SOCKET_URL=http://localhost:4000" > .env.developmentRestart npm run dev if it was already running, so Vite picks up the new environment variable.
npm run buildThe optimized production build will be created in the dist/ folder. Preview it locally with npm run preview.
whiteboard-app/
├── public/
└── src/
├── components/
│ ├── Board.jsx # Main canvas viewport, drawing, and per-user undo/redo
│ ├── CollabUI.jsx # Go Live/Join Room flow, share sheet, roster, toasts
│ ├── PeerCursors.jsx # Renders other collaborators' live cursors
│ ├── DarkModeToggle.jsx # Dark/light mode toggle button
│ ├── ThemeSelector.jsx # Base color theme dropdown menu
│ ├── Toolbar.jsx # Toolbar (draw/select tools & download)
│ ├── Toolbox.jsx # Options panel (styles, sizes, gap)
│ └── WelcomeModal.jsx # First-time visitor onboarding card
├── hooks/
│ └── useCollabSocket.js # Owns the Socket.IO connection, rooms, presence, reconnect
├── icons/
├── store/
│ ├── board-context.js # Board selection context
│ ├── BoardProvider.jsx # Board provider wrapper
│ ├── toolbox-context.js # Style selections context
│ ├── ToolboxProvider.jsx # Style provider wrapper
│ ├── collab-context.js # Collaboration state context
│ └── CollabProvider.jsx # Collaboration provider wrapper
├── utils/
│ ├── element.js # Shape generation, rendering & themes
│ ├── export.js # PNG export utility
│ ├── geometry.js # Bounds, hit testing & resizing
│ └── math.js # Arrowhead calculation & formulas
├── App.jsx
└── constants.js # Key settings and defaults
-
Live Collaboration via Socket.IO — one shared connection, never two
Loadingflowchart TD CP["CollabProvider"] -->|owns the one connection| Hook(("useCollabSocket()")) Hook --> CC["CollabContext"] CC -->|drawing events| Board["Board.jsx"] CC -->|room · presence · share| UI["CollabUI.jsx"]Both components read the same socket through context — the connection is never opened twice.
-
Per-User Undo / Redo — Ctrl+Z can't erase someone else's shape
Loadingflowchart LR Me["Your action"] --> Log["Your undo log"] Peer["Peer's action"] --> Board["Shared board"] Log -->|"Ctrl+Z reverses"| Board Peer -.->|"never enters"| LogEach client keeps a private log of only its own operations, instead of one shared timeline everyone edits.
-
Decoupled State via Context API — three contexts, one job each
Loadingflowchart TD App --> BC["BoardContext<br/>elements"] App --> TC["ToolboxContext<br/>stroke · fill · roughness"] App --> CC["CollabContext<br/>room · presence · cursors"] -
Geometry & Math Utils — hit-testing, element bounds, and resize math live in
geometry.js/math.js, out of component code and independently testable. -
Dynamic Element Redrawing — dark mode swaps colors by regenerating
roughEleat render time rather than mutating stored elements, so undo history stays intact across theme changes. -
Freehand Canvas Rendering —
perfect-freehandturns brush stroke points into an SVG path, drawn straight into the HTML5 Canvas viaPath2D. -
Touch & Pointer Support — drawing runs on Pointer Events, not mouse-only events, so the same code handles mouse, touch, and pen input identically.