BubbleMenu extension

VersionDownloads

This extension will make a contextual menu appear near a selection of text. Use it to let users apply marks to their text selection.

As always, the markup and styling is totally up to you.

If you are using a framework like React or Vue, use the frameworks specific BubbleMenu component instead of the extension. The component provides a more convenient API and handles the DOM element for you. You can find more information about the component in the Usage with frameworks section below.

Install

npm install @tiptap/extension-bubble-menu

Settings

element

The DOM element that contains your menu.

Type: HTMLElement

Default: null

In the React version of the Bubble Menu, access the DOM element with the ref prop of the BubbleMenu component, by passing a ref into it.

updateDelay

The BubbleMenu debounces the update method to allow the bubble menu to not be updated on every selection update. This can be controlled in milliseconds. The BubbleMenuPlugin will come with a default delay of 250ms. This can be deactivated, by setting the delay to 0 which deactivates the debounce.

Type: Number

Default: undefined

resizeDelay

The BubbleMenu debounces the resize calculation for the bubble menu to allow the bubble menu to not be updated on every resize event. This can be controlled in milliseconds.

Type: Number

Default: 100

options

Under the hood, the BubbleMenu Floating UI. You can control the middleware and positioning of the floating menu with these options.

Type: Object

Default: { strategy: 'absolute', placement: 'right' }

OptionTypeDescription
strategystringThe positioning strategy. See here
placementstringThe placement of the menu. See here
offsetnumber, OffsetOptions or booleanThe offset middleware options. If true use default options, if false disable the middleware
flipFlipOptions or booleanThe flip middleware options. If true use default options, if false disable the middleware
shiftShiftOptions or booleanThe shift middleware options. If true use default options, if false disable the middleware
arrowArrowOptions or falseThe arrow middleware options. If false disable the middleware
sizeSizeOptions or booleanThe size middleware options. If true use default options, if false disable the middleware
autoPlacementAutoPlacementOptions or booleanThe autoPlacement middleware options. If true use default options, if false disable the middleware
hideHideOptions or booleanThe hide middleware options. If true use default options, if false disable the middleware
inlineInlineOptions or booleanThe inline middleware options. If true use default options, if false disable the middleware
onShowFunction or undefinedA callback that is called when the menu is shown. This can be used to add custom logic or styles when the menu is displayed.
onHideFunction or undefinedA callback that is called when the menu is hidden. This can be used to add custom logic or styles when the menu is hidden.
onUpdateFunction or undefinedA callback that is called when the menu is updated. This can be used to add custom logic or styles when the menu is updated.
onDestroyFunction or undefinedA callback that is called when the menu is destroyed. This can be used to add custom logic or styles when the menu is removed.

pluginKey

The key for the underlying ProseMirror plugin. Make sure to use different keys if you add more than one instance.

Type: string | PluginKey

Default: 'bubbleMenu'

shouldShow

A callback to control whether the menu should be shown or not.

Type: (props) => boolean

appendTo

The element to which the bubble menu should be appended to in the DOM. Can be a HTMLElement or a callback function that returns a HTMLElement.

Type: HTMLElement | (() => HTMLElement) | undefined

Default: undefined, the menu will be appended to the editor's parent element (editor.view.dom.parentElement).

getReferencedVirtualElement

A callback to provide the anchor coordinates used to position the menu. Should return a virtual element as expected by Floating UI.

Type: () => VirtualElement | null

Default: null, anchor is implied by the editor selection.

Source code

packages/extension-bubble-menu/

Use the extension

JavaScript

import { Editor } from '@tiptap/core'
import BubbleMenu from '@tiptap/extension-bubble-menu'

new Editor({
  extensions: [
    BubbleMenu.configure({
      element: document.querySelector('.menu'),
    }),
  ],
})

Usage with frameworks

React

The @tiptap/react package comes with a BubbleMenu component you can import from @tiptap/react/menus. It provides the same functionality as the extension but with a React-friendly API. When using this component, you don't need to add the BubbleMenu extension to your editor.

import { BubbleMenu } from '@tiptap/react/menus'

function MyBubbleMenu({ editor }) {
  return (
    <BubbleMenu editor={editor}>
      <button onClick={() => editor.chain().focus().toggleBold().run()}>
        Bold
      </button>
      <button onClick={() => editor.chain().focus().toggleItalic().run()}>
        Italic
      </button>
    </BubbleMenu>
  )
}

Vue

The @tiptap/vue-3 package comes with a BubbleMenu component you can import from @tiptap/vue-3/menus. It provides the same functionality as the extension but with a Vue-friendly API. When using this component, you don't need to add the BubbleMenu extension to your editor.

<template>
  <BubbleMenu :editor="editor">
    <button @click="editor.chain().focus().toggleBold().run()">
      Bold
    </button>
    <button @click="editor.chain().focus().toggleItalic().run()">
      Italic
    </button>
  </BubbleMenu>
</template>

<script setup>
  import { BubbleMenu } from '@tiptap/vue-3/menus'
  
  // make sure to pass the editor instance as a prop to the BubbleMenu component
  const { editor } = defineProps({
    editor: {
      type: Object,
      required: true,
    },
  })
</script>

Note: The same menu is also available in the Vue 2 version of Tiptap, you can import it from @tiptap/vue-2/menus.

Custom logic

Customize the logic for showing the menu with the shouldShow option. For components, shouldShow can be passed as a prop.

BubbleMenu.configure({
  shouldShow: ({ editor, view, state, oldState, from, to }) => {
    // only show the bubble menu for images and links
    return editor.isActive('image') || editor.isActive('link')
  },
})

Multiple menus

Use multiple menus by setting an unique pluginKey.

import { Editor } from '@tiptap/core'
import BubbleMenu from '@tiptap/extension-bubble-menu'

new Editor({
  extensions: [
    BubbleMenu.configure({
      pluginKey: 'bubbleMenuOne',
      element: document.querySelector('.menu-one'),
    }),
    BubbleMenu.configure({
      pluginKey: 'bubbleMenuTwo',
      element: document.querySelector('.menu-two'),
    }),
  ],
})

Alternatively you can pass a ProseMirror PluginKey.

import { Editor } from '@tiptap/core'
import BubbleMenu from '@tiptap/extension-bubble-menu'
import { PluginKey } from '@tiptap/pm/state'

new Editor({
  extensions: [
    BubbleMenu.configure({
      pluginKey: new PluginKey('bubbleMenuOne'),
      element: document.querySelector('.menu-one'),
    }),
    BubbleMenu.configure({
      pluginKey: new PluginKey('bubbleMenuTwo'),
      element: document.querySelector('.menu-two'),
    }),
  ],
})

Force update the position of the bubble menu

If the bubble menu changes size after the initial render, its position will not be adjusted automatically. To fix this, you can force update the position of the bubble menu by emitting an 'updatePosition' event.

editor.commands.setMeta('bubbleMenu', 'updatePosition')