0
# Menu Components
1
2
Interactive menu components that float above or beside editor content, providing contextual editing tools and actions.
3
4
## Capabilities
5
6
### BubbleMenu Component
7
8
A floating menu that appears when text is selected, providing quick access to formatting options.
9
10
```typescript { .api }
11
/**
12
* Floating bubble menu that appears on text selection
13
* Uses Vue Teleport to render outside component tree
14
*/
15
const BubbleMenu: DefineComponent<{
16
/** Plugin identifier for registration */
17
pluginKey?: {
18
type: PropType<BubbleMenuPluginProps['pluginKey']>;
19
default: 'bubbleMenu';
20
};
21
22
/** Editor instance (required) */
23
editor: {
24
type: PropType<Editor>;
25
required: true;
26
};
27
28
/** Delay in milliseconds before updating position */
29
updateDelay?: {
30
type: PropType<number>;
31
default: undefined;
32
};
33
34
/** Delay in milliseconds before updating on resize */
35
resizeDelay?: {
36
type: PropType<number>;
37
default: undefined;
38
};
39
40
/** Additional plugin options */
41
options?: {
42
type: PropType<BubbleMenuPluginOptions>;
43
default: () => ({});
44
};
45
46
/** Custom function to determine when menu should show */
47
shouldShow?: {
48
type: PropType<(props: { editor: Editor; view: EditorView; state: EditorState; oldState?: EditorState; from: number; to: number; }) => boolean>;
49
default: null;
50
};
51
}>;
52
53
interface BubbleMenuPluginOptions {
54
/** Custom positioning element */
55
element?: HTMLElement;
56
/** Custom tippyjs options */
57
tippyOptions?: Record<string, any>;
58
/** Custom placement */
59
placement?: 'top' | 'bottom' | 'left' | 'right';
60
}
61
```
62
63
**Key Features:**
64
- Automatically positions near text selection
65
- Uses Vue Teleport to render in document body
66
- Customizable show/hide logic
67
- Configurable update and resize delays
68
- Integrates with @floating-ui/dom for positioning
69
70
**Usage Examples:**
71
72
```typescript
73
<template>
74
<div v-if="editor">
75
<BubbleMenu :editor="editor" :updateDelay="100">
76
<div class="bubble-menu">
77
<button
78
@click="editor.chain().focus().toggleBold().run()"
79
:class="{ 'is-active': editor.isActive('bold') }"
80
>
81
Bold
82
</button>
83
<button
84
@click="editor.chain().focus().toggleItalic().run()"
85
:class="{ 'is-active': editor.isActive('italic') }"
86
>
87
Italic
88
</button>
89
<button
90
@click="editor.chain().focus().toggleStrike().run()"
91
:class="{ 'is-active': editor.isActive('strike') }"
92
>
93
Strike
94
</button>
95
</div>
96
</BubbleMenu>
97
<EditorContent :editor="editor" />
98
</div>
99
</template>
100
101
<script setup>
102
import { useEditor, EditorContent, BubbleMenu } from "@tiptap/vue-3";
103
import StarterKit from "@tiptap/starter-kit";
104
105
const editor = useEditor({
106
content: '<p>Select some text to see the bubble menu!</p>',
107
extensions: [StarterKit],
108
});
109
</script>
110
111
<style>
112
.bubble-menu {
113
display: flex;
114
background: white;
115
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1), 0 10px 20px rgba(0, 0, 0, 0.1);
116
border-radius: 0.5rem;
117
overflow: hidden;
118
}
119
120
.bubble-menu button {
121
border: none;
122
background: none;
123
padding: 0.5rem;
124
cursor: pointer;
125
}
126
127
.bubble-menu button.is-active {
128
background: #e5e7eb;
129
}
130
</style>
131
```
132
133
**Custom shouldShow Logic:**
134
135
```typescript
136
<template>
137
<BubbleMenu
138
:editor="editor"
139
:shouldShow="customShouldShow"
140
>
141
<div class="custom-bubble-menu">
142
<!-- Menu content -->
143
</div>
144
</BubbleMenu>
145
</template>
146
147
<script setup>
148
const customShouldShow = ({ editor, view, state, from, to }) => {
149
// Only show for text selections (not node selections)
150
if (from === to) return false;
151
152
// Only show if selection contains bold text
153
return editor.isActive('bold');
154
};
155
</script>
156
```
157
158
### FloatingMenu Component
159
160
A floating menu that appears on empty lines, providing quick access to block-level formatting options.
161
162
```typescript { .api }
163
/**
164
* Floating menu that appears on empty lines
165
* Uses Vue Teleport to render outside component tree
166
*/
167
const FloatingMenu: DefineComponent<{
168
/** Plugin identifier for registration */
169
pluginKey?: {
170
type: null;
171
default: 'floatingMenu';
172
};
173
174
/** Editor instance (required) */
175
editor: {
176
type: PropType<Editor>;
177
required: true;
178
};
179
180
/** Additional plugin options */
181
options?: {
182
type: PropType<FloatingMenuPluginOptions>;
183
default: () => ({});
184
};
185
186
/** Custom function to determine when menu should show */
187
shouldShow?: {
188
type: PropType<(props: { editor: Editor; view: EditorView; state: EditorState; oldState?: EditorState; }) => boolean>;
189
default: null;
190
};
191
}>;
192
193
interface FloatingMenuPluginOptions {
194
/** Custom positioning element */
195
element?: HTMLElement;
196
/** Custom tippyjs options */
197
tippyOptions?: Record<string, any>;
198
/** Custom placement */
199
placement?: 'top' | 'bottom' | 'left' | 'right';
200
}
201
```
202
203
**Key Features:**
204
- Appears on empty paragraphs or lines
205
- Uses Vue Teleport for body-level rendering
206
- Customizable visibility conditions
207
- Integrates with @floating-ui/dom for positioning
208
209
**Usage Examples:**
210
211
```typescript
212
<template>
213
<div v-if="editor">
214
<FloatingMenu :editor="editor">
215
<div class="floating-menu">
216
<button @click="editor.chain().focus().toggleHeading({ level: 1 }).run()">
217
H1
218
</button>
219
<button @click="editor.chain().focus().toggleHeading({ level: 2 }).run()">
220
H2
221
</button>
222
<button @click="editor.chain().focus().toggleBulletList().run()">
223
Bullet List
224
</button>
225
<button @click="editor.chain().focus().toggleOrderedList().run()">
226
Ordered List
227
</button>
228
</div>
229
</FloatingMenu>
230
<EditorContent :editor="editor" />
231
</div>
232
</template>
233
234
<script setup>
235
import { useEditor, EditorContent, FloatingMenu } from "@tiptap/vue-3";
236
import StarterKit from "@tiptap/starter-kit";
237
238
const editor = useEditor({
239
content: '<p></p>',
240
extensions: [StarterKit],
241
});
242
</script>
243
244
<style>
245
.floating-menu {
246
display: flex;
247
background: white;
248
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1), 0 10px 20px rgba(0, 0, 0, 0.1);
249
border-radius: 0.5rem;
250
overflow: hidden;
251
}
252
253
.floating-menu button {
254
border: none;
255
background: none;
256
padding: 0.5rem;
257
cursor: pointer;
258
}
259
</style>
260
```
261
262
**Custom shouldShow for FloatingMenu:**
263
264
```typescript
265
<template>
266
<FloatingMenu
267
:editor="editor"
268
:shouldShow="customShouldShow"
269
>
270
<div class="floating-menu">
271
<!-- Menu content -->
272
</div>
273
</FloatingMenu>
274
</template>
275
276
<script setup>
277
const customShouldShow = ({ editor, view, state }) => {
278
const { selection } = state;
279
const { $anchor, empty } = selection;
280
281
// Only show on empty paragraphs
282
if (!empty) return false;
283
284
// Check if current node is a paragraph
285
if ($anchor.parent.type.name !== 'paragraph') return false;
286
287
// Only show if paragraph is empty
288
return $anchor.parent.textContent === '';
289
};
290
</script>
291
```
292
293
### Menu Styling and Positioning
294
295
**Advanced Styling:**
296
297
```typescript
298
<template>
299
<BubbleMenu
300
:editor="editor"
301
:options="{
302
placement: 'top',
303
tippyOptions: {
304
duration: 100,
305
animation: 'shift-away'
306
}
307
}"
308
>
309
<div class="menu-container">
310
<!-- Menu buttons -->
311
</div>
312
</BubbleMenu>
313
</template>
314
315
<style>
316
.menu-container {
317
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
318
border-radius: 8px;
319
padding: 8px;
320
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
321
}
322
</style>
323
```
324
325
**Multiple Menu Integration:**
326
327
```typescript
328
<template>
329
<div v-if="editor">
330
<!-- Bubble menu for text formatting -->
331
<BubbleMenu :editor="editor" pluginKey="textBubbleMenu">
332
<div class="text-menu">
333
<button @click="toggleBold">Bold</button>
334
<button @click="toggleItalic">Italic</button>
335
</div>
336
</BubbleMenu>
337
338
<!-- Floating menu for block elements -->
339
<FloatingMenu :editor="editor" pluginKey="blockFloatingMenu">
340
<div class="block-menu">
341
<button @click="addHeading">Heading</button>
342
<button @click="addList">List</button>
343
</div>
344
</FloatingMenu>
345
346
<EditorContent :editor="editor" />
347
</div>
348
</template>
349
```
350
351
## Types
352
353
```typescript { .api }
354
interface BubbleMenuPluginProps {
355
pluginKey: string | PluginKey;
356
editor: Editor;
357
element: HTMLElement;
358
updateDelay?: number;
359
resizeDelay?: number;
360
options?: Record<string, any>;
361
shouldShow?: ((props: {
362
editor: Editor;
363
view: EditorView;
364
state: EditorState;
365
oldState?: EditorState;
366
from: number;
367
to: number;
368
}) => boolean) | null;
369
}
370
371
interface FloatingMenuPluginProps {
372
pluginKey: string | PluginKey;
373
editor: Editor;
374
element: HTMLElement;
375
options?: Record<string, any>;
376
shouldShow?: ((props: {
377
editor: Editor;
378
view: EditorView;
379
state: EditorState;
380
oldState?: EditorState;
381
}) => boolean) | null;
382
}
383
```