Real-time collaboration for Object UI — live cursors, presence tracking, comment threads, and conflict resolution.
- 🖱️ Live Cursors - Display remote user cursors in real time with
LiveCursors - 👥 Presence Avatars - Show active users with
PresenceAvatars - 💬 Comment Threads - Threaded comments with @mentions via
CommentThread - 🔄 Realtime Subscriptions - WebSocket data subscriptions with
useRealtimeSubscription - 👁️ Presence Tracking - Track who's viewing or editing with
usePresence - ⚔️ Conflict Resolution - Version history and merge conflicts with
useConflictResolution - 🎯 Type-Safe - Full TypeScript support with exported types
npm install @object-ui/collaborationPeer Dependencies:
react^18.0.0 || ^19.0.0
import {
usePresence,
useRealtimeSubscription,
LiveCursors,
PresenceAvatars,
CommentThread,
type Comment,
type PresenceUser,
} from '@object-ui/collaboration';
declare const broadcastPresence: (user: PresenceUser) => void;
declare const comments: Comment[];
declare const currentUser: { id: string; name: string };
function CollaborativeEditor() {
const { users, updateCursor } = usePresence(broadcastPresence, {
user: { id: 'user-1', name: 'Alice' },
});
const { lastMessage, connectionState } = useRealtimeSubscription({
channel: 'document-123',
});
return (
<div data-connection={connectionState}>
<PresenceAvatars users={users} />
<LiveCursors users={users} />
<div onMouseMove={(event) => updateCursor({ x: event.clientX, y: event.clientY })}>
{lastMessage ? lastMessage.channel : 'waiting for updates'}
</div>
<CommentThread threadId="thread-1" comments={comments} currentUser={currentUser} />
</div>
);
}Hook for WebSocket data subscriptions. channel is the only required key, and the
result carries the connection state plus the messages received so far:
import { useRealtimeSubscription } from '@object-ui/collaboration';
const { lastMessage, messages, connectionState, error } = useRealtimeSubscription({
channel: 'orders',
});Hook for tracking user presence. The broadcast callback comes first and the configuration second; the configuration carries the current user:
import { usePresence, type PresenceUser } from '@object-ui/collaboration';
declare const broadcastPresence: (user: PresenceUser) => void;
const { users, updateCursor, currentUser } = usePresence(broadcastPresence, {
user: { id: 'user-1', name: 'Alice' },
});Hook for version history and conflict management. It is called with the current user's id, optionally their name:
import { useConflictResolution } from '@object-ui/collaboration';
const { versions, conflicts, resolveConflict } = useConflictResolution('user-1', 'Alice');Displays remote user cursors on the page:
import { LiveCursors, type PresenceUser } from '@object-ui/collaboration';
declare const presenceUsers: PresenceUser[];
const cursors = <LiveCursors users={presenceUsers} />;Shows avatar badges for active users:
import { PresenceAvatars, type PresenceUser } from '@object-ui/collaboration';
declare const presenceUsers: PresenceUser[];
const avatars = <PresenceAvatars users={presenceUsers} maxVisible={5} />;Threaded comment component with @mentions. comments and currentUser are
required; new comments arrive through onAddComment:
import { CommentThread, type Comment } from '@object-ui/collaboration';
declare const comments: Comment[];
declare const currentUser: { id: string; name: string };
declare const saveComment: (content: string, mentions: string[]) => void;
const thread = (
<CommentThread
threadId="thread-1"
comments={comments}
currentUser={currentUser}
onAddComment={(content, mentions) => saveComment(content, mentions)}
/>
);- 📦 npm package
- 📝 Changelog
- 🐛 Report an issue
- 🤝 Contributing Guide
- 🗺️ Roadmap
MIT — see LICENSE.