> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/ueberdosis/tiptap/llms.txt
> Use this file to discover all available pages before exploring further.

# BubbleMenu

> Vue component that displays a contextual menu near selected text.

## Overview

The `BubbleMenu` component renders a contextual menu that appears near the user's text selection. It's commonly used for formatting toolbars that appear when text is selected, similar to Medium or Google Docs.

## Signature

```typescript theme={null}
const BubbleMenu: DefineComponent<{
  editor: Editor
  pluginKey?: string | PluginKey
  updateDelay?: number
  resizeDelay?: number
  options?: TippyOptions
  appendTo?: Element | (() => Element) | 'parent'
  shouldShow?: (props: {
    editor: Editor
    view: EditorView
    state: EditorState
    oldState?: EditorState
    from: number
    to: number
  }) => boolean
  getReferencedVirtualElement?: (props: {
    editor: Editor
    view: EditorView
    state: EditorState
    oldState?: EditorState
    from: number
    to: number
  }) => DOMRect | Range | VirtualElement
}>
```

## Props

<ParamField path="editor" type="Editor" required>
  The Tiptap editor instance. This prop is required.
</ParamField>

<ParamField path="pluginKey" type="string | PluginKey" default="'bubbleMenu'">
  A unique key for the ProseMirror plugin. Use this if you need multiple bubble menus or want to identify this specific plugin.
</ParamField>

<ParamField path="updateDelay" type="number" default="undefined">
  Delay in milliseconds before updating the menu position after a selection change. Useful for debouncing rapid selection changes.
</ParamField>

<ParamField path="resizeDelay" type="number" default="undefined">
  Delay in milliseconds before updating the menu position after a window resize event.
</ParamField>

<ParamField path="options" type="TippyOptions" default="{}">
  Configuration options for the underlying Tippy.js positioning library. Use this to customize the menu's placement, offset, animation, and other display properties.

  Common options:

  * `placement` - Position relative to reference (e.g., 'top', 'bottom', 'left', 'right')
  * `offset` - Distance from the reference element
  * `duration` - Animation duration
  * `zIndex` - CSS z-index value
</ParamField>

<ParamField path="appendTo" type="Element | (() => Element) | 'parent'" default="undefined">
  The element to append the bubble menu to. Can be:

  * A DOM element
  * A function that returns a DOM element
  * The string `'parent'` to append to the editor's parent element

  By default, the menu is appended to `document.body`.
</ParamField>

<ParamField path="shouldShow" type="function" default="null">
  A callback function that determines whether the bubble menu should be visible. Receives an object with:

  * `editor` - The editor instance
  * `view` - The ProseMirror EditorView
  * `state` - Current editor state
  * `oldState` - Previous editor state
  * `from` - Start position of selection
  * `to` - End position of selection

  Return `true` to show the menu, `false` to hide it.

  By default, the menu shows when there's a text selection and hides when the selection is empty.
</ParamField>

<ParamField path="getReferencedVirtualElement" type="function" default="undefined">
  A function that returns a custom reference element for positioning. Use this to position the menu relative to a specific element or virtual element instead of the text selection.

  Receives the same props as `shouldShow` and should return a DOMRect, Range, or Tippy.js VirtualElement.
</ParamField>

## Lifecycle

* **onMounted**: Registers the bubble menu plugin with the editor and sets up positioning
* **onBeforeUnmount**: Unregisters the plugin from the editor

## Styling

The component inherits attributes (`inheritAttrs: false` is set, but attributes are manually applied), allowing you to add custom classes, styles, or other HTML attributes:

```vue theme={null}
<BubbleMenu :editor="editor" class="custom-bubble-menu" style="background: white;">
  <!-- content -->
</BubbleMenu>
```

## Examples

### Basic Usage

```vue theme={null}
<script setup>
import { useEditor, EditorContent, BubbleMenu } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'

const editor = useEditor({
  content: '<p>Select some text to see the bubble menu!</p>',
  extensions: [
    StarterKit,
  ],
})
</script>

<template>
  <div>
    <EditorContent :editor="editor" />
    <BubbleMenu :editor="editor" v-if="editor">
      <button @click="editor.chain().focus().toggleBold().run()">
        Bold
      </button>
      <button @click="editor.chain().focus().toggleItalic().run()">
        Italic
      </button>
    </BubbleMenu>
  </div>
</template>
```

### Styled Bubble Menu

```vue theme={null}
<script setup>
import { useEditor, EditorContent, BubbleMenu } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'

const editor = useEditor({
  content: '<p>Select text to format</p>',
  extensions: [StarterKit],
})
</script>

<template>
  <div>
    <EditorContent :editor="editor" />
    <BubbleMenu :editor="editor" v-if="editor" class="bubble-menu">
      <button 
        @click="editor.chain().focus().toggleBold().run()"
        :class="{ 'is-active': editor.isActive('bold') }"
      >
        Bold
      </button>
      <button 
        @click="editor.chain().focus().toggleItalic().run()"
        :class="{ 'is-active': editor.isActive('italic') }"
      >
        Italic
      </button>
      <button 
        @click="editor.chain().focus().toggleStrike().run()"
        :class="{ 'is-active': editor.isActive('strike') }"
      >
        Strike
      </button>
    </BubbleMenu>
  </div>
</template>

<style scoped>
.bubble-menu {
  display: flex;
  gap: 0.25rem;
  padding: 0.25rem;
  background-color: white;
  border: 1px solid #ccc;
  border-radius: 0.5rem;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.bubble-menu button {
  padding: 0.25rem 0.5rem;
  border: none;
  background: transparent;
  cursor: pointer;
  border-radius: 0.25rem;
}

.bubble-menu button:hover {
  background-color: #f0f0f0;
}

.bubble-menu button.is-active {
  background-color: #000;
  color: #fff;
}
</style>
```

### Custom shouldShow Logic

```vue theme={null}
<script setup>
import { useEditor, EditorContent, BubbleMenu } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'
import Link from '@tiptap/extension-link'

const editor = useEditor({
  content: '<p>Select a link to see the bubble menu</p>',
  extensions: [
    StarterKit,
    Link,
  ],
})

// Only show the bubble menu when a link is selected
function shouldShow({ editor, from, to }) {
  return from !== to && editor.isActive('link')
}
</script>

<template>
  <div>
    <EditorContent :editor="editor" />
    <BubbleMenu 
      :editor="editor" 
      v-if="editor"
      :shouldShow="shouldShow"
    >
      <button @click="editor.chain().focus().unsetLink().run()">
        Remove Link
      </button>
      <button @click="openLinkEditor">
        Edit Link
      </button>
    </BubbleMenu>
  </div>
</template>
```

### With Tippy Options

```vue theme={null}
<script setup>
import { useEditor, EditorContent, BubbleMenu } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'

const editor = useEditor({
  content: '<p>Select text</p>',
  extensions: [StarterKit],
})

const tippyOptions = {
  placement: 'top',
  duration: 100,
  offset: [0, 10],
  zIndex: 1000,
}
</script>

<template>
  <div>
    <EditorContent :editor="editor" />
    <BubbleMenu 
      :editor="editor" 
      v-if="editor"
      :options="tippyOptions"
    >
      <div class="menu-content">
        <button @click="editor.chain().focus().toggleBold().run()">
          Bold
        </button>
      </div>
    </BubbleMenu>
  </div>
</template>
```

### Multiple Bubble Menus

```vue theme={null}
<script setup>
import { useEditor, EditorContent, BubbleMenu } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'
import Link from '@tiptap/extension-link'
import Image from '@tiptap/extension-image'

const editor = useEditor({
  content: '<p>Different bubble menus for different contexts</p>',
  extensions: [StarterKit, Link, Image],
})

function shouldShowTextMenu({ editor, from, to }) {
  return from !== to && !editor.isActive('link') && !editor.isActive('image')
}

function shouldShowLinkMenu({ editor }) {
  return editor.isActive('link')
}

function shouldShowImageMenu({ editor }) {
  return editor.isActive('image')
}
</script>

<template>
  <div>
    <EditorContent :editor="editor" />
    
    <!-- Text formatting menu -->
    <BubbleMenu 
      :editor="editor" 
      v-if="editor"
      pluginKey="textMenu"
      :shouldShow="shouldShowTextMenu"
    >
      <button @click="editor.chain().focus().toggleBold().run()">
        Bold
      </button>
      <button @click="editor.chain().focus().toggleItalic().run()">
        Italic
      </button>
    </BubbleMenu>
    
    <!-- Link menu -->
    <BubbleMenu 
      :editor="editor" 
      v-if="editor"
      pluginKey="linkMenu"
      :shouldShow="shouldShowLinkMenu"
    >
      <button @click="editor.chain().focus().unsetLink().run()">
        Remove Link
      </button>
    </BubbleMenu>
    
    <!-- Image menu -->
    <BubbleMenu 
      :editor="editor" 
      v-if="editor"
      pluginKey="imageMenu"
      :shouldShow="shouldShowImageMenu"
    >
      <button @click="editor.chain().focus().deleteSelection().run()">
        Delete Image
      </button>
    </BubbleMenu>
  </div>
</template>
```

## Notes

* The bubble menu is automatically positioned near the text selection using Tippy.js
* By default, it shows only when there's a non-empty text selection
* The component renders as a `<div>` element that you can style with classes or inline styles
* Multiple bubble menus can coexist by using different `pluginKey` values
* The menu is removed from the DOM and re-parented by the plugin when shown
* Use `shouldShow` to customize when the menu appears based on editor state

## See Also

* [FloatingMenu](/api/vue/floating-menu) - Menu for empty lines
* [useEditor](/api/vue/use-editor) - Create editor instances
* [EditorContent](/api/vue/editor-content) - Render the editor
