This guide shows you how to build a Google Docs-style collaborative text editor using CollabKit’s Yjs provider with TipTap.
What You’ll Build
- A rich text editor where multiple users can type simultaneously
- Conflict-free collaborative editing via CRDTs
- Persistent document state (survives page refreshes)
- User cursors and presence indicators
Prerequisites
- A CollabKit server running with a room and users created
@collab-kit/client installed
Step 1: Install Dependencies
npm install @collab-kit/client @collab-kit/utils yjs \
@tiptap/core @tiptap/starter-kit @tiptap/extension-collaboration
Step 2: Set Up CollabKit
import CollabKitClient from '@collab-kit/client';
const client = new CollabKitClient({
serverUrl: 'https://api.collab-kit.com',
authToken: '<jwt-token>',
});
await client.connect();
await client.join();
Step 3: Create the Yjs Document and Provider
import { CollabKitYjsProvider } from '@collab-kit/client/yjs';
import * as Y from 'yjs';
const ydoc = new Y.Doc();
const provider = new CollabKitYjsProvider(client, ydoc, {
documentId: 'main-editor', // Unique per document in the room
});
Step 4: Initialize TipTap
import { Editor } from '@tiptap/core';
import StarterKit from '@tiptap/starter-kit';
import Collaboration from '@tiptap/extension-collaboration';
const editor = new Editor({
element: document.getElementById('editor'),
extensions: [
StarterKit.configure({
history: false, // Disable built-in undo -- Yjs handles this
}),
Collaboration.configure({
document: ydoc,
}),
],
editable: false, // Start disabled until synced
});
// Enable editing once the initial document state loads
provider.on('synced', () => {
editor.setEditable(true);
console.log('Document ready for editing');
});
Always disable TipTap’s built-in history extension when using Collaboration. Yjs provides its own undo/redo management that understands collaborative edits.
Step 5: Add a Loading State
Show a loading indicator until the document syncs:
const loadingEl = document.getElementById('loading');
provider.on('synced', () => {
loadingEl.style.display = 'none';
editor.setEditable(true);
});
Step 6: Add User Presence (Optional)
Show cursor positions of other users alongside the editor:
// Share cursor/selection position
editor.on('selectionUpdate', ({ editor }) => {
const { from, to } = editor.state.selection;
client.presence.update({
cursor: { from, to },
user: { name: client.currentUser?.name },
});
});
// Display other users' selections
client.presence.sync('*', ({ userId, state }) => {
if (userId === client.userId || !state) return;
// Use state.cursor.from and state.cursor.to to highlight
// their selection in the editor (implementation depends on your UI)
console.log(`${state.user.name} is at position ${state.cursor.from}-${state.cursor.to}`);
});
Step 7: Clean Up
window.addEventListener('beforeunload', () => {
provider.destroy();
editor.destroy();
void client.disconnect();
});
Multiple Documents Per Room
You can have multiple collaborative documents in the same room by using different documentId values:
const notesDoc = new Y.Doc();
const notesProvider = new CollabKitYjsProvider(client, notesDoc, {
documentId: 'meeting-notes',
});
const agendaDoc = new Y.Doc();
const agendaProvider = new CollabKitYjsProvider(client, agendaDoc, {
documentId: 'meeting-agenda',
});
Next Steps