Suggestion plugin for Tiptap that provides triggered autocomplete functionality for mentions, hashtags, and other contextual suggestions
npx @tessl/cli install tessl/npm-tiptap--suggestion@3.4.00
# Tiptap Suggestion
1
2
Tiptap Suggestion is a utility plugin for Tiptap editors that enables triggered autocomplete functionality such as mentions, hashtags, slash commands, and other contextual suggestions. It provides a flexible ProseMirror plugin that detects trigger characters and offers lifecycle hooks for rendering custom suggestion interfaces.
3
4
## Package Information
5
6
- **Package Name**: @tiptap/suggestion
7
- **Package Type**: npm
8
- **Language**: TypeScript
9
- **Installation**: `npm install @tiptap/suggestion`
10
11
## Core Imports
12
13
```typescript
14
import { Suggestion, exitSuggestion, findSuggestionMatch, SuggestionPluginKey } from "@tiptap/suggestion";
15
```
16
17
For default import:
18
19
```typescript
20
import Suggestion from "@tiptap/suggestion";
21
```
22
23
For CommonJS:
24
25
```javascript
26
const { Suggestion, exitSuggestion, findSuggestionMatch, SuggestionPluginKey } = require("@tiptap/suggestion");
27
```
28
29
## Basic Usage
30
31
```typescript
32
import { Editor } from "@tiptap/core";
33
import { Suggestion } from "@tiptap/suggestion";
34
35
const editor = new Editor({
36
// ... other config
37
});
38
39
// Create a suggestion plugin for mentions
40
const mentionSuggestion = Suggestion({
41
editor: editor,
42
char: '@',
43
items: ({ query }) => {
44
return [
45
{ id: 1, label: 'John Doe' },
46
{ id: 2, label: 'Jane Smith' },
47
{ id: 3, label: 'Bob Wilson' }
48
].filter(item =>
49
item.label.toLowerCase().includes(query.toLowerCase())
50
);
51
},
52
render: () => ({
53
onStart: (props) => {
54
// Create and show suggestion dropdown
55
console.log('Suggestion started', props.query);
56
},
57
onUpdate: (props) => {
58
// Update suggestion dropdown with new items
59
console.log('Suggestion updated', props.items);
60
},
61
onExit: () => {
62
// Hide suggestion dropdown
63
console.log('Suggestion exited');
64
},
65
onKeyDown: ({ event }) => {
66
// Handle keyboard navigation
67
if (event.key === 'ArrowUp' || event.key === 'ArrowDown') {
68
return true; // Handled
69
}
70
return false; // Not handled
71
}
72
}),
73
command: ({ editor, range, props }) => {
74
// Insert the selected mention
75
editor.chain().focus().insertContentAt(range, `@${props.label}`).run();
76
},
77
});
78
79
// Add the plugin to your editor
80
editor.registerPlugin(mentionSuggestion);
81
```
82
83
## Capabilities
84
85
### Suggestion Plugin Creation
86
87
Creates a ProseMirror plugin that handles suggestion functionality with customizable behavior and rendering.
88
89
```typescript { .api }
90
/**
91
* Creates a suggestion plugin for Tiptap editors
92
* @param options - Configuration options for the suggestion behavior
93
* @returns ProseMirror Plugin instance
94
*/
95
function Suggestion<I = any, TSelected = any>(
96
options: SuggestionOptions<I, TSelected>
97
): Plugin<any>;
98
99
interface SuggestionOptions<I = any, TSelected = any> {
100
/** The plugin key for the suggestion plugin (default: SuggestionPluginKey) */
101
pluginKey?: PluginKey;
102
/** The editor instance (required) */
103
editor: Editor;
104
/** The character that triggers the suggestion (default: '@') */
105
char?: string;
106
/** Allow spaces in the suggestion query (default: false) */
107
allowSpaces?: boolean;
108
/** Allow the trigger character to be included in the query (default: false) */
109
allowToIncludeChar?: boolean;
110
/** Allowed prefix characters before trigger (default: [' ']) */
111
allowedPrefixes?: string[] | null;
112
/** Only match suggestions at the start of the line (default: false) */
113
startOfLine?: boolean;
114
/** HTML tag name for decoration node (default: 'span') */
115
decorationTag?: string;
116
/** CSS class for decoration node (default: 'suggestion') */
117
decorationClass?: string;
118
/** Content for decoration node (default: '') */
119
decorationContent?: string;
120
/** CSS class when decoration is empty (default: 'is-empty') */
121
decorationEmptyClass?: string;
122
/** Function called when suggestion is selected */
123
command?: (props: { editor: Editor; range: Range; props: TSelected }) => void;
124
/** Function returning suggestion items array */
125
items?: (props: { query: string; editor: Editor }) => I[] | Promise<I[]>;
126
/** Function returning render lifecycle hooks */
127
render?: () => {
128
/** Called before suggestion starts */
129
onBeforeStart?: (props: SuggestionProps<I, TSelected>) => void;
130
/** Called when suggestion starts */
131
onStart?: (props: SuggestionProps<I, TSelected>) => void;
132
/** Called before suggestion updates */
133
onBeforeUpdate?: (props: SuggestionProps<I, TSelected>) => void;
134
/** Called when suggestion updates */
135
onUpdate?: (props: SuggestionProps<I, TSelected>) => void;
136
/** Called when suggestion exits */
137
onExit?: (props: SuggestionProps<I, TSelected>) => void;
138
/** Called on keydown events, return true if handled */
139
onKeyDown?: (props: SuggestionKeyDownProps) => boolean;
140
};
141
/** Function determining if suggestion should be active */
142
allow?: (props: { editor: Editor; state: EditorState; range: Range; isActive?: boolean }) => boolean;
143
/** Custom match finding function */
144
findSuggestionMatch?: typeof findSuggestionMatch;
145
}
146
```
147
148
### Suggestion Match Finding
149
150
Finds suggestion matches in text based on configurable trigger patterns.
151
152
```typescript { .api }
153
/**
154
* Finds suggestion matches in text based on trigger configuration
155
* @param config - Trigger configuration object
156
* @returns SuggestionMatch object or null if no match found
157
*/
158
function findSuggestionMatch(config: Trigger): SuggestionMatch;
159
160
interface Trigger {
161
/** Trigger character */
162
char: string;
163
/** Whether to allow spaces in queries */
164
allowSpaces: boolean;
165
/** Whether to include trigger character in queries */
166
allowToIncludeChar: boolean;
167
/** Array of allowed prefix characters */
168
allowedPrefixes: string[] | null;
169
/** Whether to only match at line start */
170
startOfLine: boolean;
171
/** ProseMirror resolved position */
172
$position: ResolvedPos;
173
}
174
175
type SuggestionMatch = {
176
/** Range object with from/to positions */
177
range: Range;
178
/** Matched query string (excluding trigger character) */
179
query: string;
180
/** Full matched text (including trigger character) */
181
text: string;
182
} | null;
183
```
184
185
### Programmatic Exit
186
187
Programmatically exits suggestion mode.
188
189
```typescript { .api }
190
/**
191
* Programmatically exits the suggestion mode
192
* @param view - EditorView instance
193
* @param pluginKeyRef - PluginKey instance (default: SuggestionPluginKey)
194
*/
195
function exitSuggestion(view: EditorView, pluginKeyRef?: PluginKey): void;
196
```
197
198
### Default Plugin Key
199
200
Default PluginKey instance used by the suggestion plugin when no custom pluginKey is provided.
201
202
```typescript { .api }
203
/**
204
* Default plugin key for the suggestion plugin
205
* Used when no custom pluginKey is specified in SuggestionOptions
206
*/
207
const SuggestionPluginKey: PluginKey<any>;
208
```
209
210
## Types
211
212
### Core Interfaces
213
214
```typescript { .api }
215
interface SuggestionProps<I = any, TSelected = any> {
216
/** The editor instance */
217
editor: Editor;
218
/** The range of the suggestion text */
219
range: Range;
220
/** Current query string (excluding trigger character) */
221
query: string;
222
/** Full suggestion text (including trigger character) */
223
text: string;
224
/** Array of suggestion items */
225
items: I[];
226
/** Function to execute selected suggestion */
227
command: (props: TSelected) => void;
228
/** HTML element of the decoration node */
229
decorationNode: Element | null;
230
/** Function returning DOMRect for positioning */
231
clientRect?: (() => DOMRect | null) | null;
232
}
233
234
interface SuggestionKeyDownProps {
235
/** EditorView instance */
236
view: EditorView;
237
/** KeyboardEvent */
238
event: KeyboardEvent;
239
/** Current suggestion range */
240
range: Range;
241
}
242
```
243
244
### External Dependencies
245
246
```typescript { .api }
247
// From @tiptap/core
248
interface Editor { /* Tiptap editor instance */ }
249
interface Range { from: number; to: number; }
250
251
// From @tiptap/pm/state
252
class Plugin { /* ProseMirror plugin class */ }
253
class PluginKey { /* ProseMirror plugin key class */ }
254
interface EditorState { /* ProseMirror editor state */ }
255
256
// From @tiptap/pm/view
257
interface EditorView { /* ProseMirror editor view */ }
258
259
// From @tiptap/pm/model
260
interface ResolvedPos { /* ProseMirror resolved position */ }
261
```
262
263
## Usage Examples
264
265
### Hashtag Suggestions
266
267
```typescript
268
import { Suggestion } from "@tiptap/suggestion";
269
270
const hashtagSuggestion = Suggestion({
271
editor: myEditor,
272
char: '#',
273
items: ({ query }) => {
274
return ['javascript', 'typescript', 'react', 'vue']
275
.filter(tag => tag.includes(query.toLowerCase()))
276
.map(tag => ({ tag }));
277
},
278
render: () => ({
279
onStart: (props) => {
280
// Show hashtag dropdown
281
},
282
onUpdate: (props) => {
283
// Update hashtag list
284
},
285
onExit: () => {
286
// Hide dropdown
287
}
288
}),
289
command: ({ editor, range, props }) => {
290
editor.chain().focus().insertContentAt(range, `#${props.tag}`).run();
291
}
292
});
293
```
294
295
### Slash Commands
296
297
```typescript
298
import { Suggestion } from "@tiptap/suggestion";
299
300
const slashCommandSuggestion = Suggestion({
301
editor: myEditor,
302
char: '/',
303
startOfLine: true,
304
items: ({ query }) => {
305
return [
306
{ title: 'Heading 1', command: 'heading', level: 1 },
307
{ title: 'Heading 2', command: 'heading', level: 2 },
308
{ title: 'Bullet List', command: 'bulletList' },
309
].filter(item =>
310
item.title.toLowerCase().includes(query.toLowerCase())
311
);
312
},
313
command: ({ editor, range, props }) => {
314
editor.chain().focus().deleteRange(range);
315
316
if (props.command === 'heading') {
317
editor.chain().setHeading({ level: props.level }).run();
318
} else if (props.command === 'bulletList') {
319
editor.chain().toggleBulletList().run();
320
}
321
}
322
});
323
```
324
325
### Custom Match Pattern
326
327
```typescript
328
import { Suggestion, findSuggestionMatch } from "@tiptap/suggestion";
329
330
const customSuggestion = Suggestion({
331
editor: myEditor,
332
char: '$',
333
allowSpaces: true,
334
allowedPrefixes: [' ', '(', '['],
335
findSuggestionMatch: (config) => {
336
// Custom matching logic
337
return findSuggestionMatch(config);
338
},
339
items: ({ query }) => {
340
// Return variable suggestions
341
return variables.filter(v => v.name.includes(query));
342
}
343
});
344
```