Suggestion utility
This utility helps with all kinds of suggestions in the editor. Have a look at the Mention or Emoji node to see it in action.
Settings
char
The character that triggers the autocomplete popup.
Default: '@'
pluginKey
A ProseMirror PluginKey.
Default: SuggestionPluginKey
allow
A function that returns a boolean to indicate if the suggestion should be active.
Default: (props: { editor: Editor; state: EditorState; range: Range, isActive?: boolean }) => true
shouldShow
A function that returns a boolean to indicate if the suggestion should be active. This is useful to prevent suggestions from opening for remote users in collaborative environments.
shouldShow: ({ editor, range, query, text, transaction }) => {
return !isChangeOrigin(transaction)
}Default: null
shouldResetDismissed
Controls when a dismissed suggestion becomes active again after the user closes it with Escape or exitSuggestion().
By default, the utility keeps the dismissed suggestion closed while the user stays in the same trigger context. With allowSpaces: true, that dismissed context can span spaces until the cursor leaves it.
Use shouldResetDismissed if you need to decide yourself when that dismissed context should be cleared.
The callback is not called for every transaction. It is only evaluated on transactions where the suggestion plugin finds a valid match, the match passes allow and shouldShow, and there is a dismissed suggestion state to potentially clear.
Suggestion({
// ...
shouldResetDismissed: ({ transaction, allowSpaces, range, match }) => {
if (!allowSpaces) {
return false
}
return transaction.doc.textBetween(range.from, match.range.to, '\n').includes('.')
},
})Return true to clear the dismissed state for the current transaction and allow the suggestion to open again.
Default: null
allowSpaces
Allows or disallows spaces in suggested items.
Default: false
allowedPrefixes
The prefix characters that are allowed to trigger a suggestion. Set to null to allow any prefix character.
Default: [' ']
startOfLine
Trigger the autocomplete popup at the start of a line only.
Default: false
minQueryLength
The minimum number of characters the user must type after the trigger character before the items() function is called.
Useful when fetching suggestions from an API — it prevents unnecessary network requests when the query is too short to be meaningful.
Default: 0
Suggestion({
minQueryLength: 2,
items: async ({ query, signal }) => {
// Only called when query.length >= 2
const response = await fetch(`/api/search?q=${query}`, { signal })
return response.json()
},
})debounce
The number of milliseconds to wait after the user stops typing before calling items(). This prevents expensive operations (like API requests) from being triggered on every keystroke.
The debounce timer resets on each keystroke, so the items() function is only called once the user pauses typing for the specified duration.
Default: 0 (no debounce)
Suggestion({
debounce: 300,
items: async ({ query }) => {
// Called 300ms after the user stops typing
return searchApi(query)
},
})initialItems
An array of items shown immediately when the suggestion popup opens, before the async items() call resolves. After items() completes, its result replaces the initial items.
Useful for showing recent or popular items while the real results are loading.
Suggestion({
initialItems: ['Lea Thompson', 'Cyndi Lauper', 'Tom Cruise'],
items: async ({ query }) => {
// These replace the initial items once resolved
return fetchSearchResults(query)
},
})Default: undefined
decorationTag
The HTML tag that should be rendered for the suggestion.
Default: 'span'
decorationClass
A CSS class that should be added to the suggestion.
Default: 'suggestion'
decorationContent
The content that should be rendered in the suggestion decoration.
Default: ''
decorationEmptyClass
A CSS class that should be added to the suggestion when it is empty.
Default: 'is-empty'
placement
The preferred placement of the suggestion popup relative to the cursor or trigger decoration. Uses Floating UI placement values.
The resolved placement is forwarded to SuggestionProps.floatingUi.placement, which you can read in your render callbacks.
Default: 'bottom-start'
Suggestion({
placement: 'top-start',
})offset
Offsets the suggestion popup from its anchor by the specified number of pixels on each axis. These values are forwarded to the Floating UI offset middleware.
Default: {}
Suggestion({
offset: { mainAxis: 8, crossAxis: 4 },
})flip
When enabled, the popup automatically flips to the opposite side when there isn't enough space in the preferred direction. This toggles the Floating UI flip middleware.
Default: true
Suggestion({
placement: 'top-start',
flip: false,
})container
A CSS selector string or an HTMLElement that defines the container for the suggestion popup. Setting this can help with scroll containment or z-index stacking contexts.
Default: undefined
Suggestion({
container: '#my-editor-container',
})floatingUi
Advanced Floating UI configuration for cases where the default options don't offer enough control. When provided, these options take precedence over the top-level placement, offset, and flip settings.
import { flip, shift, size } from '@floating-ui/dom'
Suggestion({
floatingUi: {
placement: 'top-start',
strategy: 'fixed',
middleware: [
flip({ padding: 8 }),
shift({ padding: 8 }),
],
},
})All properties are optional. The full type is:
type SuggestionFloatingUiOptions = {
placement?: 'top' | 'top-start' | 'top-end'
| 'bottom' | 'bottom-start' | 'bottom-end'
| 'right' | 'right-start' | 'right-end'
| 'left' | 'left-start' | 'left-end'
strategy?: 'absolute' | 'fixed'
middleware?: Middleware[]
}Default: undefined
command
Executed when a suggestion is selected.
Default: () => {}
items
A function or async function that returns filtered suggestions based on user input. The function receives an AbortSignal (signal) which is aborted when the user continues typing (e.g., a new keystroke makes the current request stale). Use this to cancel in-progress fetch requests.
items: async ({ editor, query, signal }) => {
const response = await fetch(`/api/search?q=${query}`, { signal })
return response.json()
}Default: ({ editor, query, signal }) => []
render
A render function for the autocomplete popup. Returns an object with lifecycle hooks (onStart, onBeforeStart, onUpdate, onBeforeUpdate, onExit, onKeyDown) that receive a SuggestionProps object.
The SuggestionProps object passed to each hook includes the following additional properties when items() is async:
{
// ... existing props (editor, range, query, text, items, command, decorationNode, clientRect) ...
/** True while an async items() call is in progress, false otherwise */
loading: boolean
/** Resolved Floating UI configuration passed through from the options */
floatingUi: {
placement: SuggestionPlacement
strategy: 'absolute' | 'fixed'
middleware: Middleware[]
offset: { mainAxis?: number; crossAxis?: number }
}
}Default: () => ({})
findSuggestionMatch
Optional param to replace the built-in regex matching of editor content that triggers a suggestion. See the source for more detail.
Default: findSuggestionMatch(config: Trigger): SuggestionMatch
Exiting open suggestions
Sometimes you want your users to be able to exit an an open suggestion without selecting an item. To achieve this, users can either press Escape which will close the open suggestion.
If you want to manually trigger the closing of the suggestion, you can use use exitSuggestion utility function to close existing suggestions on your view.
import { exitSuggestion } from '@tiptap/suggestion'
import { PluginKey } from 'prosemirror-state' // optional, if you need to create a custom key
const MySuggestionPluginKey = new PluginKey('my-suggestions') // or use the default 'suggestion'
exitSuggestion(editor.view, MySuggestionPluginKey)
// Alternatively, use the default plugin key:
// exitSuggestion(editor.view, 'suggestion')Collaboration
When using Tiptap in a collaborative environment, you might notice that suggestions (like mentions) pop open for all users when one user triggers them. This happens because the document change is synced to all clients, and each client's suggestion plugin reacts to the change.
To prevent this, use the shouldShow option combined with the isChangeOrigin helper from @tiptap/extension-collaboration.
import { isChangeOrigin } from '@tiptap/extension-collaboration'
Suggestion({
// …
shouldShow: ({ transaction }) => {
return !isChangeOrigin(transaction)
},
})By returning !isChangeOrigin(props.transaction), the suggestion will only be activated if the current user initiated the change.