---
title: "Suggestion Menu"
description: "A flexible autocomplete and suggestion system for Tiptap editors with trigger characters, filtering, keyboard navigation, and extensible item support."
canonical_url: "https://tiptap.dev/docs/ui-components/utils-components/suggestion-menu"
---

# Suggestion Menu

A flexible autocomplete and suggestion system for Tiptap editors with trigger characters, filtering, keyboard navigation, and extensible item support.

A powerful and flexible suggestion menu system for Tiptap editors. Creates floating dropdown menus triggered by configurable characters with full keyboard navigation, filtering, and customizable rendering support.

> **Interactive demo:** [suggestion menu](https://template.tiptap.dev/preview/tiptap-ui-utils/suggestion-menu)

## Installation

Add the component via the Tiptap CLI:

```bash
npx @tiptap/cli@latest add suggestion-menu
```

## Components

### `<SuggestionMenu />`

The core suggestion menu component that provides a floating dropdown interface triggered by typing specific characters.

#### Usage

```tsx
<SuggestionMenu
  editor={editor}
  char="@"
  pluginKey="myMentionMenu"
  items={async ({ query, editor }) => [
    {
      title: 'John Doe',
      subtext: 'Software Engineer',
      onSelect: ({ editor, range }) => {
        editor.chain().focus().insertContentAt(range, '@john').run()
      },
    },
  ]}
>
  {({ items, selectedIndex, onSelect }) => (
    <MenuList items={items} selectedIndex={selectedIndex} onSelect={onSelect} />
  )}
</SuggestionMenu>
```

#### Props

| Name                 | Type                                   | Default                    | Description                                                                                  |
| -------------------- | -------------------------------------- | -------------------------- | -------------------------------------------------------------------------------------------- |
| `editor`             | `Editor \| null`                       | `undefined`                | The Tiptap editor instance                                                                   |
| `char`               | `string`                               | `"@"`                      | Character that triggers the suggestion menu                                                  |
| `items`              | `(props) => Item[] \| Promise<Item[]>` | `() => []`                 | Function returning suggestion items                                                          |
| `children`           | `(props) => ReactNode`                 | `undefined`                | Render function for menu content                                                             |
| `floatingOptions`    | `Partial<UseFloatingOptions>`          | `undefined`                | Additional floating UI positioning options                                                   |
| `selector`           | `string`                               | `"tiptap-suggestion-menu"` | CSS selector for the menu container                                                          |
| `pluginKey`          | `string \| PluginKey`                  | `SuggestionPluginKey`      | Unique identifier for the suggestion plugin                                                  |
| `maxHeight`          | `number`                               | `384`                      | Maximum height of the suggestion menu in pixels. Menu scrolls if content exceeds this height |
| `allowSpaces`        | `boolean`                              | `false`                    | Allow spaces in suggestion queries                                                           |
| `allowToIncludeChar` | `boolean`                              | `false`                    | Include trigger character in query                                                           |
| `allowedPrefixes`    | `string[] \| null`                     | `[" "]`                    | Characters that can precede the trigger                                                      |
| `startOfLine`        | `boolean`                              | `false`                    | Only trigger at line start                                                                   |
| `decorationTag`      | `string`                               | `"span"`                   | HTML tag for decoration element                                                              |
| `decorationClass`    | `string`                               | `"suggestion"`             | CSS class for decoration styling                                                             |
| `decorationContent`  | `string`                               | `""`                       | Placeholder text in decoration                                                               |

## Types

### `SuggestionItem<T>`

Interface defining the structure of suggestion items.

```tsx
interface SuggestionItem<T = any> {
  title: string // Main display text
  subtext?: string // Secondary context text
  badge?: React.MemoExoticComponent<IconComponent> | React.FC<IconProps> | string // Icon or badge component
  group?: string // Group identifier for organization
  keywords?: string[] // Additional search keywords
  context?: T // Custom data passed to onSelect
  onSelect: (props: {
    // Selection handler
    editor: Editor
    range: Range
    context?: T
  }) => void
}
```

### `SuggestionMenuRenderProps<T>`

Props passed to the children render function.

```tsx
type SuggestionMenuRenderProps<T = any> = {
  items: SuggestionItem<T>[] // Filtered suggestion items
  selectedIndex?: number // Currently selected item index
  onSelect: (item: SuggestionItem<T>) => void // Item selection handler
}
```

## Utilities

### `filterSuggestionItems(items, query)`

Filters and prioritizes suggestion items based on a search query.

```tsx
import { filterSuggestionItems } from '@/components/tiptap-ui-utils/suggestion-menu'

const filteredItems = filterSuggestionItems(allItems, 'john')
```

#### Parameters

| Name    | Type               | Description                         |
| ------- | ------------------ | ----------------------------------- |
| `items` | `SuggestionItem[]` | Array of suggestion items to filter |
| `query` | `string`           | Search query string                 |

#### Filtering Logic

- Matches against `title`, `subtext`, and `keywords` properties
- Case-insensitive matching
- Prioritizes exact matches and "starts with" matches
- Returns all items for empty queries

### `calculateStartPosition(cursorPosition, previousNode, triggerChar)`

Calculates the start position of a suggestion command in the text.

```tsx
import { calculateStartPosition } from '@/components/tiptap-ui-utils/suggestion-menu'

const startPos = calculateStartPosition(100, textNode, '@')
```

#### Parameters

| Name             | Type           | Description                          |
| ---------------- | -------------- | ------------------------------------ |
| `cursorPosition` | `number`       | Current cursor position in document  |
| `previousNode`   | `Node \| null` | Text node before cursor              |
| `triggerChar`    | `string`       | Character that triggered suggestions |

## Advanced Usage

### Custom Suggestion Menu

Create a custom suggestion menu with full control over items and rendering:

```tsx
function CustomMentionMenu() {
  const getSuggestionItems = async ({ query, editor }) => {
    const users = await fetchUsers(query)

    return users.map((user) => ({
      title: user.name,
      subtext: user.email,
      context: user,
      badge: UserIcon,
      keywords: [user.department, user.role],
      onSelect: ({ editor, range, context }) => {
        editor
          .chain()
          .focus()
          .insertContentAt(range, {
            type: 'mention',
            attrs: { id: context.id, label: context.name },
          })
          .run()
      },
    }))
  }

  return (
    <SuggestionMenu
      char="@"
      pluginKey="customMention"
      items={getSuggestionItems}
      allowSpaces={false}
      decorationClass="my-mention-decoration"
    >
      {({ items, selectedIndex, onSelect }) => (
        <Card>
          <CardBody>
            {items.map((item, index) => (
              <MentionItem
                key={item.title}
                item={item}
                isSelected={index === selectedIndex}
                onSelect={() => onSelect(item)}
              />
            ))}
          </CardBody>
        </Card>
      )}
    </SuggestionMenu>
  )
}
```

### With Grouped Items

Organize suggestions into groups for better navigation:

```tsx
function GroupedSuggestionMenu() {
  const getSuggestionItems = async ({ query, editor }) => {
    return [
      {
        title: 'Add Heading',
        group: 'Formatting',
        badge: HeadingIcon,
        onSelect: ({ editor, range }) => {
          editor.chain().focus().deleteRange(range).toggleHeading({ level: 1 }).run()
        },
      },
      {
        title: 'Add Table',
        group: 'Insert',
        badge: TableIcon,
        onSelect: ({ editor, range }) => {
          editor.chain().focus().deleteRange(range).insertTable().run()
        },
      },
    ]
  }

  return (
    <SuggestionMenu char="/" items={getSuggestionItems}>
      {({ items, selectedIndex, onSelect }) => (
        <Card>
          <CardBody>
            {Object.entries(groupBy(items, 'group')).map(([groupName, groupItems]) => (
              <div key={groupName}>
                <CardGroupLabel>{groupName}</CardGroupLabel>
                <CardItemGroup>
                  {groupItems.map((item, index) => (
                    <SuggestionItem
                      key={item.title}
                      item={item}
                      isSelected={index === selectedIndex}
                      onSelect={() => onSelect(item)}
                    />
                  ))}
                </CardItemGroup>
              </div>
            ))}
          </CardBody>
        </Card>
      )}
    </SuggestionMenu>
  )
}
```

## Styling

The suggestion menu sets a CSS custom property that you can use for styling:

### CSS Custom Properties

| Property                       | Description                                        | Default Value |
| ------------------------------ | -------------------------------------------------- | ------------- |
| `--suggestion-menu-max-height` | Maximum height of the menu, dynamically calculated | `384px`       |

The menu container has the class `tiptap-suggestion-menu` and includes a `data-selector` attribute for targeted styling.

### Example Styling

```css
.tiptap-suggestion-menu {
  max-height: var(--suggestion-menu-max-height);
  overflow-y: auto;
  background: white;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
```

## Accessibility

The component includes built-in accessibility features:

- `role="listbox"` for proper screen reader support
- `aria-label="Suggestions"` to describe the menu purpose
- Keyboard navigation with arrow keys and Enter to select
- Escape key to close the menu
- Prevents default pointer events to maintain editor focus
