---
title: "Suggestion utility"
description: "Customize autocomplete suggestions using nodes like Mention and Emoji. Explore settings and configurations in our docs."
canonical_url: "https://tiptap.dev/docs/editor/api/utilities/suggestion"
---

# Suggestion utility

Customize autocomplete suggestions using nodes like Mention and Emoji. Explore settings and configurations in our docs.

This utility helps with all kinds of suggestions in the editor. Have a look at the [`Mention`](https://tiptap.dev/docs/editor/extensions/nodes/mention.md) or [`Emoji`](https://tiptap.dev/docs/editor/extensions/nodes/emoji.md) 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.

```js
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.

```js
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`

```js
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)

```js
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.

```js
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](https://floating-ui.com/docs/computePosition#placement).

The resolved placement is forwarded to `SuggestionProps.floatingUi.placement`, which you can read in your render callbacks.

Default: `'bottom-start'`

```js
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](https://floating-ui.com/docs/offset).

Default: `{}`

```js
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](https://floating-ui.com/docs/flip).

Default: `true`

```js
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`

```js
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.

```js
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:

```ts
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.

```js
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:

```ts
{
  // ... 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](https://github.com/ueberdosis/tiptap/blob/main/packages/suggestion/src/findSuggestionMatch.ts#L18)
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.

```js
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`.

```js
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.

## Source code

[packages/suggestion/](https://github.com/ueberdosis/tiptap/blob/main/packages/suggestion/)
