Creating an editor with Tracked Changes
Learn how to create a fully functional editor with tracked changes, including a toolbar to toggle tracking and accept or reject suggestions.
Framework note
This guide uses React, but the same concepts apply to Vue and any other framework supported by Tiptap.
Prerequisites
Make sure you have the Tracked Changes extension installed. See the installation guide for details.
Set up the editor
Start by creating an editor instance with the TrackedChanges extension. You'll want to pass in the current user's ID and metadata so suggestions are attributed correctly.
import { useEditor, EditorContent } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
import { TrackedChanges } from '@tiptap-pro/extension-tracked-changes'
function Editor() {
const editor = useEditor({
extensions: [
StarterKit,
TrackedChanges.configure({
enabled: true,
userId: 'user-123',
userMetadata: {
name: 'John Doe',
},
}),
],
content: '<p>Start editing to see tracked changes in action.</p>',
})
if (!editor) {
return null
}
return <EditorContent editor={editor} />
}At this point, every edit the user makes is tracked as a suggestion. Insertions appear as add suggestions and deletions as delete suggestions.
Add a toolbar
Next, add controls to toggle tracking and accept or reject suggestions. The extension provides editor commands for all of these operations.
import { useCallback, useState } from 'react'
import { useEditor, EditorContent } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
import { TrackedChanges } from '@tiptap-pro/extension-tracked-changes'
function Editor() {
const [isEnabled, setIsEnabled] = useState(true)
const editor = useEditor({
extensions: [
StarterKit,
TrackedChanges.configure({
enabled: true,
userId: 'user-123',
userMetadata: { name: 'John Doe' },
}),
],
content: '<p>Start editing to see tracked changes in action.</p>',
})
const toggleTrackedChanges = useCallback(() => {
if (!editor) return
editor.commands.toggleTrackedChanges()
setIsEnabled(!isEnabled)
}, [editor, isEnabled])
if (!editor) {
return null
}
return (
<div>
<div className="toolbar">
<button onClick={toggleTrackedChanges}>
{isEnabled ? 'Disable' : 'Enable'} Track Changes
</button>
<button onClick={() => editor.commands.acceptAllSuggestions()}>
Accept All
</button>
<button onClick={() => editor.commands.rejectAllSuggestions()}>
Reject All
</button>
</div>
<EditorContent editor={editor} />
</div>
)
}Add suggestion styles
Suggestions render as <span> elements with data attributes. Add CSS to visually distinguish insertions from deletions.
/* Insertions — green highlight */
[data-suggestion-type="add"] {
background-color: rgba(0, 255, 0, 0.15);
text-decoration: underline;
}
/* Deletions — red strikethrough */
[data-suggestion-type="delete"] {
background-color: rgba(255, 0, 0, 0.15);
text-decoration: line-through;
}
/* Replacement deletions */
[data-suggestion-type="replaceDeletion"] {
background-color: rgba(255, 0, 0, 0.15);
text-decoration: line-through;
}
/* Replacement insertions */
[data-suggestion-type="replaceInsertion"] {
background-color: rgba(0, 255, 0, 0.15);
text-decoration: underline;
}See the Styling reference for all available data attributes.
Accept and reject at the cursor
You can also accept or reject the suggestion at the current cursor position without passing an ID:
<button onClick={() => editor.commands.acceptSuggestion()}>
Accept at cursor
</button>
<button onClick={() => editor.commands.rejectSuggestion()}>
Reject at cursor
</button>Accept and reject in a selection
To accept or reject all suggestions within the current text selection:
const acceptInSelection = useCallback(() => {
if (!editor) return
const { from, to } = editor.state.selection
editor.commands.acceptSuggestionsInRange({ from, to })
}, [editor])
const rejectInSelection = useCallback(() => {
if (!editor) return
const { from, to } = editor.state.selection
editor.commands.rejectSuggestionsInRange({ from, to })
}, [editor])Switch users
In a multi-user application, you'll switch the tracked changes user when the active user changes. Use the setTrackedChangesUser command:
editor.commands.setTrackedChangesUser({
userId: 'user-456',
userMetadata: { name: 'Jane Smith' },
})Enable code block tracking
By default, code blocks don't support marks. The extension provides a helper to patch any code block extension for tracked changes support:
import CodeBlock from '@tiptap/extension-code-block'
import { TrackedChanges, withSuggestionMarkOnCodeBlock } from '@tiptap-pro/extension-tracked-changes'
const TrackableCodeBlock = withSuggestionMarkOnCodeBlock(CodeBlock)
const editor = useEditor({
extensions: [
StarterKit.configure({ codeBlock: false }),
TrackableCodeBlock,
TrackedChanges.configure({ enabled: true, userId: 'user-123' }),
],
})Next steps
- Build a suggestion list sidebar to display all suggestions
- Learn about suggestion grouping and advanced behaviors
- Explore the full commands API