A high-performance, real-time collaborative SVG design tool built for the modern web.
🚀 Live Demo · ✨ Features · 🛠️ Tech Stack · ⚙️ Installation · 🙋 Author
Most collaborative design tools are either too heavyweight or locked behind paywalls. Ocular is a focused, open-source alternative that demonstrates what's possible when you pair a modern React stack with real-time infrastructure — no plugins, no downloads, just a browser.
It was built to answer a specific question: can you build a production-grade collaborative canvas — with live cursors, shared state, undo history, and a full property panel — entirely on the web platform?
The answer is yes. Ocular does it with an SVG canvas, Liveblocks for conflict-free real-time sync, and a clean Next.js App Router architecture. Every architectural decision — from the bitmask-based resize handles to the normalized pencil-draft coordinate system — reflects deliberate, considered engineering, not boilerplate.
Open it in two browser tabs side by side to see live collaboration in action. Create a room, invite yourself from the second tab, and watch cursors, selections, and edits sync in real time.
- Infinite SVG canvas with smooth pan (click-and-drag) and zoom (scroll wheel,
+/-buttons, clamped to 0.5x–2x) - Rectangle tool — with adjustable corner radius
- Ellipse tool — full ellipse and circle support
- Freehand pencil tool — pressure-sensitive variable-width strokes powered by
perfect-freehand, with live draft preview - Text tool — double-click any text layer to edit inline; supports font family, size, and weight controls
- Canvas background color — customize the canvas background via the color picker when nothing is selected
- Click to select any layer; the selection box shows live dimensions (e.g.
200x100) - Selection net — click-drag on empty canvas to rubber-band select multiple layers at once
- Multi-layer translate — move all selected layers together
- 8-handle resize — drag any corner or edge-midpoint handle; supports flipping (dragging past the opposite edge). Resize is intentionally disabled for freehand path layers since their geometry is point-based, not box-based
- Right-click context menu — bring any selected layer to front or send it to back, repositioning it in the
LiveListz-order
Selecting a layer reveals a live property panel with controls for:
| Property | Applies to |
|---|---|
| Position (X, Y) | All layers |
| Dimensions (W, H) | Rectangle, Ellipse, Text |
| Opacity (0–1) | All layers |
| Corner Radius | Rectangle only |
| Fill color | All layers |
| Stroke color | All layers |
| Font Family | Text only (Inter, Arial, Times New Roman) |
| Font Size | Text only |
| Font Weight | Text only (100–900) |
All changes sync to Liveblocks storage and are immediately visible to every collaborator in the room.
This is Ocular's defining feature.
- Live cursors — every collaborator's pointer is rendered on your canvas in real time, with a color-coded avatar showing their initial
- Live pencil drafts — see collaborators' freehand strokes appear stroke-by-stroke as they draw, before they even lift their pointer
- Shared selections — layers selected by other users are highlighted for you
- Conflict-free shared state — layer storage lives in a Liveblocks
LiveMap; layer ordering in aLiveList. Concurrent edits are resolved automatically with CRDTs - Full undo/redo history —
Ctrl+Z/Ctrl+Shift+Zworks across all operations, including layer insertions and property edits. LiveblocksuseHistorymanages this without any custom diffing logic - Collaborator avatar row — the right sidebar header shows live avatars for everyone currently in the room
- Live-updating list of all layers in reverse z-order (newest on top)
- Click any layer in the panel to select it on the canvas
- Highlights the currently selected layer(s)
- Email + password authentication via NextAuth v5 (Auth.js) with bcrypt hashing
- Design rooms — create unlimited rooms from your dashboard; each room maps to a Liveblocks room with scoped access
- Invite collaborators by email — send access to any registered user via the Share modal; invitees get full edit access
- Revoke access — remove a collaborator from a room at any time
- Room ownership — only the room owner can rename or delete a room. Invited users can leave (revoke their own access)
- Access control — unauthenticated users are redirected by Next.js middleware; canvas pages 404 for users without access
- "My Designs" and "Shared with me" tabs for quick navigation
- Color-coded gradient thumbnails for each room (deterministic, based on room ID)
- Inline title editing — click the pencil icon or double-click the title field;
Enterto save,Escapeto cancel Backspaceto delete a selected room card (with a confirmation modal)- Double-click any room card to open the canvas
| Shortcut | Action |
|---|---|
Backspace |
Delete selected layer(s) / Delete selected room (dashboard) |
Ctrl + Z |
Undo |
Ctrl + Shift + Z |
Redo |
Ctrl + A |
Select all layers |
Enter / Double-click |
Open design (dashboard) |
Double-click (text layer) |
Edit text inline |
The canvas is a desktop-first experience. On screens narrower than 992px, users see a graceful fallback screen with a "Copy page URL" button so they can switch to a larger device without losing their link.
| Category | Technology |
|---|---|
| Framework | Next.js 15 (App Router, Turbopack) |
| UI Library | React 19 |
| Language | TypeScript |
| Styling | Tailwind CSS v4 with OKLCH color tokens |
| Real-time | Liveblocks (Presence, Storage, History) |
| Authentication | NextAuth v5 / Auth.js — Credentials provider + JWT sessions |
| ORM | Prisma |
| Database | PostgreSQL |
| Freehand Drawing | perfect-freehand |
| Color Picker | react-colorful |
| Icons | Lucide React |
| ID Generation | nanoid |
| Env Validation | @t3-oss/env-nextjs + Zod |
| Package Manager | pnpm |
| Deployment | Vercel |
User ──< Room (owned)
User ──< RoomInvitation >── Room
- User — stores email, bcrypt-hashed password, and NextAuth account/session records
- Room — owned by a single user; title defaults to
"Untitled" - RoomInvitation — join table connecting invitees to rooms; enforces a unique constraint on
(roomId, inviteeId)to prevent duplicate invites
- Node.js ≥ 18
- pnpm ≥ 10 —
npm install -g pnpm - A PostgreSQL database (local or hosted, e.g. Neon, Supabase)
- A Liveblocks account (free tier works)
git clone https://github.com/KeepSerene/ocular-figma-clone.git
cd ocular-figma-clonepnpm installCreate a .env file in the project root:
# ── Database ──────────────────────────────────────────────────
DATABASE_URL="postgresql://USER:PASSWORD@HOST:PORT/DATABASE"
# ── NextAuth ──────────────────────────────────────────────────
# Generate with: openssl rand -base64 32
AUTH_SECRET="your-auth-secret"
# ── Liveblocks ────────────────────────────────────────────────
LIVEBLOCKS_PUBLIC_KEY="pk_..."
LIVEBLOCKS_SECRET_KEY="sk_..."
# ── App URL ───────────────────────────────────────────────────
NEXT_PUBLIC_APP_URL="http://localhost:3000"Getting your Liveblocks keys: Sign in at liveblocks.io, create a project, and copy the public and secret keys from the API Keys page.
# Push the schema to your database
pnpm db:push
# Or run migrations (recommended for production)
pnpm db:migratepnpm devOpen http://localhost:3000 in your browser.
| Script | Description |
|---|---|
pnpm dev |
Start dev server with Turbopack |
pnpm build |
Build for production |
pnpm start |
Start production server |
pnpm check |
Lint + type-check |
pnpm typecheck |
TypeScript type-check only |
pnpm db:push |
Push Prisma schema to database |
pnpm db:migrate |
Run pending migrations |
pnpm db:generate |
Generate a new migration |
pnpm db:studio |
Open Prisma Studio |
- Push your repository to GitHub
- Import the project on Vercel
- Add all environment variables from your
.envfile in the Vercel project settings - Set
NEXT_PUBLIC_APP_URLto your deployed URL (e.g.https://your-app.vercel.app) - Deploy — Vercel handles the rest
Note:
AUTH_SECRETis required in production. The app will refuse to build without it.
ocular-figma-clone/
├── src/
│ ├── app/ # Next.js App Router
│ │ ├── (auth)/ # Sign-in / Sign-up pages
│ │ ├── dashboard/
│ │ │ └── designs/[designId]/ # Canvas page (Liveblocks room)
│ │ └── api/
│ │ └── liveblocks-auth/ # Liveblocks auth endpoint
│ ├── components/
│ │ ├── canvas/ # SVG canvas + all layer components
│ │ ├── toolbar/ # Bottom toolbar (tools, undo, zoom)
│ │ ├── liveblocks/ # Room provider, live presence
│ │ ├── landing/ # Landing page sections
│ │ └── dashboard/ # Dashboard UI components
│ ├── actions/ # Next.js Server Actions (auth, rooms)
│ ├── server/
│ │ ├── auth/ # NextAuth config
│ │ ├── db.ts # Prisma client singleton
│ │ └── liveblocks.ts # Liveblocks server client
│ ├── lib/utils.ts # Canvas utilities (coord transforms, path math)
│ ├── types.ts # All shared TypeScript types & enums
│ └── env.js # Type-safe env validation
├── prisma/
│ └── schema.prisma # Database schema
├── liveblocks.config.ts # Liveblocks type declarations
└── middleware.ts # Route protection (dashboard auth guard)
Dhrubajyoti Bhattacharjee
A full-stack developer with a focus on building real-time, interactive web applications with clean architecture and strong UX sensibility.
Licensed under the Apache 2.0 License.
If you found this project interesting or learned something from it, consider giving it a ⭐ on GitHub — it helps a lot!