@veltdev/ace-crdt-react and @veltdev/ace-crdt libraries connect Ace Editor to a Velt CRDT text Store backed by Yjs. Shared content lives in one Y.Text; cursors, selections, and attention highlights use transient awareness state.
Prerequisites
- Node.js (v14 or higher)
- A Velt account with an API key (sign up)
- React (v18 or higher) when using the React wrapper
- Optional: TypeScript for type safety
Setup
Step 1: Install Dependencies
- React / Next.js
- Other Frameworks
ace-builds@^1.44.0. Keep one copy of @veltdev/crdt, yjs, and y-protocols in the final application bundle.
Step 2: Setup Velt
Initialize Velt, authenticate a user, and set stable document identity before constructing the collaboration manager. Follow the Velt Setup Docs for the production token endpoint used byauthProvider.
- React / Next.js
- Other Frameworks
editorId.
Step 3: Create the Ace Editor
The host needs stable dimensions. Import each mode and theme before selecting it.- React / Next.js
- Other Frameworks
AceCrdtEditor creates a react-ace editor. When using the hook with an editor your application owns, create Ace normally and pass the instance to the hook:Step 4: Initialize Collaboration
Ace markers require a real AceRange. The factory below also widens a collapsed cursor by one column so the remote caret stays visible.
- React Component
- React Hook
- Other Frameworks
value or defaultValue to AceCrdtEditor. The shared Y.Text is the content source of truth.React Context Provider (Optional)
React applications can expose one hook-managed collaboration value through context:CollaborationProvider is an alias of AceCrdtProvider. Use one provider or one hook for an editor, not both.
Step 5: Add Remote Marker Styles
The manager injects per-user marker colors and labels dynamically. Keep the editor sized and add a shared shape rule:border: none.
Step 6: Monitor Status and Remote Selections
- React / Next.js
- Other Frameworks
The component render function and hook expose reactive
status, synced, remoteSelections, and stats values:Step 7: Initial Content and Force Reset
Initial content is plain text and is applied only when the shared text is new and uninitialized:Step 8: Work with Shared Text
insertText() accepts an optional character index. Without one, the manager uses the current Ace cursor when available.
Step 9: Configure Collaborative Undo and Redo
The manager creates one YjsUndoManager for the shared text.
- React / Next.js
- Other Frameworks
Step 10: Save and Restore Versions
Step 11: Subscribe to CRDT Events
- React / Next.js
- Other Frameworks
Step 12: Attach or Replace an Editor (Alternative)
Use the attach-later lifecycle only when the Store must initialize before Ace:- React / Next.js
- Other Frameworks
editor to createCollaboration(). Do not use both paths in one initialization flow.
Step 13: Custom Encryption (Optional)
- React / Next.js
- Other Frameworks
Step 14: Clean Up
- React / Next.js
- Other Frameworks
useCollaboration() and AceCrdtEditor unsubscribe and destroy their collaboration manager on unmount. When an app owns the Ace instance, use an explicit manager-first cleanup before destroying Ace:destroy() safely preserves the required order.Notes
- Plain-text model: Ace modes and themes are local presentation; only text is stored in
Y.Text. - One binding: Passing
editorto the factory attaches it automatically. - Range factory: Use Ace’s verified
Rangeconstructor for remote markers. - Automatic selections: The manager attaches Ace selection listeners and publishes cursor/selection state through awareness.
- Advanced escape hatches:
getDoc(),getYText(),getProvider(),getAwareness(), andgetUndoManager()are for diagnostics or advanced integrations.getText()andgetYText()return the same sharedY.Text; internally,getYText()delegates togetText(). Do not create a second Y.Doc, provider, or shared text. - Production authentication: Use a server-backed React
authProviderand never expose privileged server tokens in browser code.
Testing and Debugging
- Open the same Velt document and Ace
editorIdas two different authenticated users in separate browser profiles. - Type and delete concurrently at different positions.
- Verify visible cursor carets, selected ranges, attention highlights, Yjs undo/redo, versions, reconnect behavior, and persistence.
- Repeat with the production mode, theme, worker, and bundler configuration.
- Mode or theme is missing: import the matching
ace-builds/src-noconflictmodule before creating Ace. - Text does not sync: remove React
value/defaultValueand confirm both clients use the same IDs. - Markers do not appear: provide
cursorData, the completerangeFactory, a sized host, and marker CSS. - Duplicate Yjs warning: deduplicate
yjsand@veltdev/crdtin the application bundle.
Complete Example
- React / Next.js
- Other Frameworks
Complete App.tsx
Complete styles.css
How It Works
- The app initializes Velt, authenticates the user, sets the document, and creates one Ace editor.
- The manager creates a Velt text Store, shared
Y.Text, provider, awareness bridge, and YjsUndoManager. - Ace deltas are converted to character offsets and applied as Yjs insert/delete transactions.
- Remote Yjs updates are applied back to Ace without echoing into a second local transaction.
- Ace selection events publish awareness state; the range factory converts remote row/column data into visible Ace markers.
- Cleanup removes subscriptions and markers before the app destroys Ace and its DOM.
APIs
React: AceCrdtEditor
React: useCollaboration()
The hook returnseditorRef, editor and manager state, shared text helpers, selection/highlight helpers, collaborative undo()/redo(), versions, statistics, attachment methods, and destroy(). It includes isSynced, versions, refreshSnapshots(), and refreshVersions() alongside the save and restore helpers. value, remoteSelections, and stats are reactive snapshots.
CollaborationManager Methods
Exported Types and Helpers
AceEditorLike,AcePosition,AceSelectionRange,AceDelta,AceRangeFactoryAceCursorData,AceAwarenessSelection,RemoteSelectionCollaborationConfig,CollaborationPrimitives,CollaborationStats,BindEditorOptionsnormalizeText(),positionToIndex(),indexToPosition(),rangeToOffsets(),offsetsToRange()

