Style suggestions with the AI Toolkit
When the AI makes changes to the document, you can display them as suggestions in the editor. In this guide, you'll learn how to customize the suggestions' appearance by modifying their styles and displaying custom elements like menus inside them.
Add CSS styles to suggestions
When the default renderDecorations function is used, the following classes are applied:
.tiptap-ai-suggestion: Applied on the content that is covered by the suggestion range..tiptap-ai-suggestion--selected: Applied when the cursor is inside the suggestion range..tiptap-ai-suggestion--change-group: Applied when the suggestion is a group of changes instead of a single change..tiptap-ai-suggestion-sub-change: Applied on individual sub-changes within a change group..tiptap-ai-suggestion-diff: Applied on the diff widget that is displayed next to the suggestion range..tiptap-ai-suggestion-diff--selected: Applied to the diff widget when the cursor is inside the suggestion range..tiptap-ai-suggestion-diff--change-group: Applied when the diff widget is a group of changes instead of a single change..tiptap-ai-suggestion-diff-sub-change: Applied on individual sub-changes within a diff change group.
Use the above classes to style the suggestions, or provide a custom renderDecorations function for full control over the UI.
Example CSS styles
We provide example CSS styles to get you started.
Preview mode
Use these styles in Preview mode, to preview changes before they are inserted into the document.
/* Highlight deleted text in red */
.tiptap-ai-suggestion,
.tiptap-ai-suggestion > * {
background-color: oklch(80.8% 0.114 19.571);
color: oklch(0.396 0.141 25.723);
}
/* Lighter background for change groups (sub-changes carry the stronger highlight) */
.tiptap-ai-suggestion.tiptap-ai-suggestion--change-group,
.tiptap-ai-suggestion.tiptap-ai-suggestion--change-group > *:not(.tiptap-ai-suggestion-sub-change) {
background-color: oklch(0.936 0.032 17.717);
}
/* Highlight sub-changes within inline groups */
.tiptap-ai-suggestion-sub-change {
background-color: oklch(80.8% 0.114 19.571);
}
/* Highlight inserted text in green */
.tiptap-ai-suggestion-diff,
.tiptap-ai-suggestion-diff > * {
background-color: oklch(87.1% 0.15 154.449);
}
/* Lighter background for diff change groups (sub-changes carry the stronger highlight) */
.tiptap-ai-suggestion-diff.tiptap-ai-suggestion-diff--change-group,
.tiptap-ai-suggestion-diff.tiptap-ai-suggestion-diff--change-group
> *:not(.tiptap-ai-suggestion-diff-sub-change) {
background-color: oklch(0.962 0.044 156.743);
}
/* Highlight sub-changes within the replacement diff widget */
.tiptap-ai-suggestion-diff-sub-change {
background-color: oklch(87.1% 0.15 154.449);
}
/* Render table row insertions correctly */
.tiptap-ai-suggestion-diff:has(tr) {
display: contents;
}
.tiptap-ai-suggestion-diff:has(tr) td,
.tiptap-ai-suggestion-diff:has(tr) th {
background-color: oklch(87.1% 0.15 154.449);
}Review mode / Compare documents
Use these styles in Review mode, to review changes after inserting them in the document.
Also use these styles when displaying suggestions generated by the Compare Documents feature.
/* Highlight inserted text in green */
.tiptap-ai-suggestion,
.tiptap-ai-suggestion > * {
background-color: oklch(87.1% 0.15 154.449);
}
/* Lighter background for change groups (sub-changes carry the stronger highlight) */
.tiptap-ai-suggestion.tiptap-ai-suggestion--change-group,
.tiptap-ai-suggestion.tiptap-ai-suggestion--change-group > *:not(.tiptap-ai-suggestion-sub-change) {
background-color: oklch(0.962 0.044 156.743);
}
/* Highlight sub-changes within inline groups */
.tiptap-ai-suggestion-sub-change {
background-color: oklch(87.1% 0.15 154.449);
}
/* Highlight deleted text in red */
.tiptap-ai-suggestion-diff,
.tiptap-ai-suggestion-diff > * {
background-color: oklch(80.8% 0.114 19.571);
color: oklch(0.396 0.141 25.723);
}
/* Lighter background for diff change groups (sub-changes carry the stronger highlight) */
.tiptap-ai-suggestion-diff.tiptap-ai-suggestion-diff--change-group,
.tiptap-ai-suggestion-diff.tiptap-ai-suggestion-diff--change-group
> *:not(.tiptap-ai-suggestion-diff-sub-change) {
background-color: oklch(0.936 0.032 17.717);
}
/* Highlight sub-changes within the replacement diff widget */
.tiptap-ai-suggestion-diff-sub-change {
background-color: oklch(80.8% 0.114 19.571);
}
/* Render table row deletions correctly */
.tiptap-ai-suggestion-diff:has(tr) {
display: contents;
}
.tiptap-ai-suggestion-diff:has(tr) td,
.tiptap-ai-suggestion-diff:has(tr) th {
background-color: oklch(80.8% 0.114 19.571);
}Render React components inside a suggestion
You can use the renderDecorations option, combined with React portals, to display a React component inside a suggestion.
First, define a React component with a hook to store the HTML element where the React Portal will be rendered.
import { useState } from 'react'
function MyComponent() {
const [portalElement, setPortalElement] = useState<HTMLElement | null>(null)
return null
}Then, in the displayOptions.renderDecorations option, create a widget decoration with a HTML element that will be used as the entry point of the React portal.
const aiToolkit = getAiToolkit(editor)
// Example method of the AI Toolkit that generates suggestions
aiToolkit.executeTool({
reviewOptions: {
mode: 'preview',
displayOptions: {
renderDecorations({ suggestion, defaultRenderDecorations }) {
const decorations = defaultRenderDecorations()
decorations.push(
Decoration.widget(suggestion.range.to, () => {
const element = document.createElement('span')
setPortalElement(element)
return element
}),
)
return decorations
},
},
},
})Finally, in the React component, render any React elements inside the portal.
import { useState } from 'react'
import { createPortal } from 'react-dom'
function MyComponent() {
const [portalElement, setPortalElement] = useState<HTMLElement | null>(null)
if (portalElement) {
return <>{createPortal(<div>Hello, world!</div>, portalElement)}</>
}
return null
}Show a popover or a tooltip when a suggestion is selected
You can use the technique explained above to show a tooltip when a suggestion is selected.
To show a popover when you select a suggestion, you need to use the getCustomSuggestionDecoration option. This function allows you to add custom elements to the suggestions, including popovers.
Below is a simplified example on how to do it with the React UI library.
import { useState } from 'react'
import { createPortal } from 'react-dom'
// Inside the React component
function MyComponent() {
const toolkit = getAiToolkit(editor)
// First, define a hook to store the HTML element where the popover will be rendered
const [popoverElement, setPopoverElement] = useState<HTMLElement | null>(null)
// When the tool is executed, configure how the decorations are rendered
aiToolkit.executeTool({
reviewOptions: {
mode: 'preview',
displayOptions: {
renderDecorations({ suggestion, defaultRenderDecorations }) {
const decorations = defaultRenderDecorations()
// Then, create a Prosemirror decoration that contains the HTML element
decorations.push(
Decoration.widget(suggestion.range.to, () => {
const element = document.createElement('span')
setPopoverElement(element)
return element
}),
)
return decorations
},
},
},
})
const selectedSuggestion = toolkit.getSelectedSuggestion()
if (popoverElement && selectedSuggestion) {
// Then, add the content to the custom element. In this example, we use React Portals to render the popover.
return <>{createPortal(<Popover suggestion={selectedSuggestion} />, popoverElement)}</>
}
return null
}We recommend using the Floating UI library to display the popover.
When you render the suggestion in the popover, use the getNextWord and getPreviousWord utility functions to show the previous and next words of the sentence where the suggestion is located.
import { getNextWord, getPreviousWord } from '@tiptap-pro/ai-toolkit'
// Get the previous word in the sentence.
const { previousWord } = getPreviousWord(editor, suggestion.range.from)
// Get the next word in the sentence and the punctuation mark that follows it, if it's the end of the sentence.
const { nextWord, punctuationMark } = getNextWord(editor, suggestion.range.to)Display suggestions in a sidebar outside the editor
You can access the current suggestions from the AI Toolkit's getSuggestions method.
const toolkit = getAiToolkit(editor)
const suggestions = toolkit.getSuggestions()Then, you can use this data to render suggestions in the UI, outside the editor. Here is an example of how to do it with the React UI library:
// Get the suggestions from the AI Toolkit
const toolkit = getAiToolkit(editor)
const suggestions = toolkit.getSuggestions()
// Render the suggestions in the UI
return (
<div>
{suggestions.map((suggestion) => (
<div key={suggestion.id}>
<p>Delete: {toolkit.getTextRange(suggestion.range)}</p>
<p>Add: {suggestion.replacementOptions[0].content.toString()}</p>
</div>
))}
</div>
)Next steps
- Learn more about suggestions in the API reference. See the
displayOptionsparameter for all the customization options.