> ## 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

> Contextual menu component that appears near the user selection

## Overview

The `BubbleMenu` component renders a contextual menu that appears when text is selected in the editor. It's positioned using Floating UI and provides a flexible way to show formatting options or other tools near the selection.

## Type Signature

```typescript theme={null}
const BubbleMenu: React.ForwardRefExoticComponent<
  BubbleMenuProps & React.RefAttributes<HTMLDivElement>
>

type BubbleMenuProps = Omit<BubbleMenuPluginProps, 'element'> & 
  React.HTMLAttributes<HTMLDivElement>
```

## Props

<ParamField path="editor" type="Editor | null">
  The editor instance. If not provided, the component will attempt to use the editor from React context via `useCurrentEditor`.
</ParamField>

<ParamField path="pluginKey" type="string | PluginKey" default="'bubbleMenu'">
  Unique identifier for this bubble menu plugin. Use different keys if you have multiple bubble menus.
</ParamField>

<ParamField path="updateDelay" type="number" default="250">
  Delay in milliseconds before the menu position is updated. This debounces position updates for better performance.
</ParamField>

<ParamField path="resizeDelay" type="number" default="60">
  Delay in milliseconds before the menu position is updated on window resize. This debounces resize events for better performance.
</ParamField>

<ParamField path="shouldShow" type="function | null" default="null">
  Function that determines whether the menu should be shown. Receives editor state and selection information.

  ```typescript theme={null}
  shouldShow?: (props: {
    editor: Editor
    element: HTMLElement
    view: EditorView
    state: EditorState
    oldState?: EditorState
    from: number
    to: number
  }) => boolean
  ```

  Default behavior: Shows when there's a non-empty text selection and the editor has focus.
</ParamField>

<ParamField path="appendTo" type="HTMLElement | (() => HTMLElement)">
  The DOM element to append the menu to. Useful when you need to render the menu in a specific container for z-index or overflow reasons.

  Default: The editor's parent element.
</ParamField>

<ParamField path="getReferencedVirtualElement" type="() => VirtualElement | null">
  Function that returns a custom virtual element for positioning. Useful for custom positioning logic.

  Default: Positions based on the current selection.
</ParamField>

<ParamField path="options" type="object">
  Floating UI configuration options for positioning and behavior.

  <Expandable title="properties">
    <ParamField path="strategy" type="'absolute' | 'fixed'" default="'absolute'">
      Positioning strategy for the menu.
    </ParamField>

    <ParamField path="placement" type="Placement" default="'top'">
      Preferred placement of the menu relative to the selection. Options: `'top'`, `'right'`, `'bottom'`, `'left'`, or any `-start`/`-end` variant.
    </ParamField>

    <ParamField path="offset" type="number | object | boolean" default="8">
      Offset from the reference element in pixels. Can be a number, detailed offset configuration, or `false` to disable.
    </ParamField>

    <ParamField path="flip" type="object | boolean" default="{}">
      Enable flipping to the opposite placement if there's not enough space. Pass an object for configuration or `false` to disable.
    </ParamField>

    <ParamField path="shift" type="object | boolean" default="{}">
      Enable shifting along the axis to keep the menu in view. Pass an object for configuration or `false` to disable.
    </ParamField>

    <ParamField path="arrow" type="object | false" default="false">
      Configuration for an arrow element pointing to the reference. Pass an object with `element` reference or `false` to disable.
    </ParamField>

    <ParamField path="size" type="object | boolean" default="false">
      Enable size adjustments to fit the menu within the viewport. Pass an object for configuration or `false` to disable.
    </ParamField>

    <ParamField path="autoPlacement" type="object | boolean" default="false">
      Automatically choose the placement with the most space. Pass an object for configuration or `false` to disable.
    </ParamField>

    <ParamField path="hide" type="object | boolean" default="false">
      Hide the menu when the reference is hidden or has escaped the boundary. Pass an object for configuration or `false` to disable.
    </ParamField>

    <ParamField path="inline" type="object | boolean" default="false">
      Improve positioning for inline reference elements. Pass an object for configuration or `false` to disable.
    </ParamField>

    <ParamField path="scrollTarget" type="HTMLElement | Window" default="window">
      The scrollable element to listen to for scroll events. The menu position will update when this element is scrolled.
    </ParamField>

    <ParamField path="onShow" type="() => void">
      Callback function called when the menu is shown.
    </ParamField>

    <ParamField path="onHide" type="() => void">
      Callback function called when the menu is hidden.
    </ParamField>

    <ParamField path="onUpdate" type="() => void">
      Callback function called when the menu position is updated.
    </ParamField>

    <ParamField path="onDestroy" type="() => void">
      Callback function called when the menu is destroyed.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField path="children" type="React.ReactNode" required>
  The content to render inside the bubble menu.
</ParamField>

<ParamField path="...props" type="React.HTMLAttributes<HTMLDivElement>">
  All standard HTML div attributes (className, style, etc.) are supported.
</ParamField>

## Usage Examples

### Basic Usage

```tsx theme={null}
import { useEditor, EditorContent, BubbleMenu } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'

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

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

### With Custom Styling

```tsx theme={null}
import { useEditor, EditorContent, BubbleMenu } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'

function MyEditor() {
  const editor = useEditor({
    extensions: [StarterKit],
    content: '<p>Select text to see a styled bubble menu</p>',
  })

  return (
    <>
      <EditorContent editor={editor} />
      <BubbleMenu
        editor={editor}
        className="bubble-menu"
        style={{
          background: '#ffffff',
          border: '1px solid #ccc',
          borderRadius: '8px',
          padding: '8px',
          boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
        }}
      >
        <button onClick={() => editor?.chain().focus().toggleBold().run()}>
          Bold
        </button>
        <button onClick={() => editor?.chain().focus().toggleItalic().run()}>
          Italic
        </button>
      </BubbleMenu>
    </>
  )
}
```

### Custom shouldShow Logic

```tsx theme={null}
import { useEditor, EditorContent, BubbleMenu } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
import Link from '@tiptap/extension-link'

function MyEditor() {
  const editor = useEditor({
    extensions: [StarterKit, Link],
    content: '<p>Select a <a href="#">link</a> to see options</p>',
  })

  return (
    <>
      <EditorContent editor={editor} />
      <BubbleMenu
        editor={editor}
        shouldShow={({ editor, from, to }) => {
          // Only show when a link is selected
          return editor.isActive('link')
        }}
      >
        <button onClick={() => editor?.chain().focus().unsetLink().run()}>
          Remove Link
        </button>
        <button onClick={() => {
          const url = window.prompt('URL')
          if (url) {
            editor?.chain().focus().setLink({ href: url }).run()
          }
        }}>
          Edit Link
        </button>
      </BubbleMenu>
    </>
  )
}
```

### Multiple Bubble Menus

```tsx theme={null}
import { useEditor, EditorContent, BubbleMenu } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
import Link from '@tiptap/extension-link'
import Image from '@tiptap/extension-image'

function MyEditor() {
  const editor = useEditor({
    extensions: [StarterKit, Link, Image],
  })

  return (
    <>
      <EditorContent editor={editor} />
      
      {/* Text formatting menu */}
      <BubbleMenu
        editor={editor}
        pluginKey="textMenu"
        shouldShow={({ editor }) => {
          return !editor.isActive('link') && !editor.isActive('image')
        }}
      >
        <button onClick={() => editor?.chain().focus().toggleBold().run()}>
          Bold
        </button>
        <button onClick={() => editor?.chain().focus().toggleItalic().run()}>
          Italic
        </button>
      </BubbleMenu>
      
      {/* Link menu */}
      <BubbleMenu
        editor={editor}
        pluginKey="linkMenu"
        shouldShow={({ editor }) => editor.isActive('link')}
      >
        <button onClick={() => editor?.chain().focus().unsetLink().run()}>
          Remove Link
        </button>
      </BubbleMenu>
      
      {/* Image menu */}
      <BubbleMenu
        editor={editor}
        pluginKey="imageMenu"
        shouldShow={({ editor }) => editor.isActive('image')}
      >
        <button onClick={() => editor?.chain().focus().deleteSelection().run()}>
          Remove Image
        </button>
      </BubbleMenu>
    </>
  )
}
```

### With Active States

```tsx theme={null}
import { useEditor, EditorContent, BubbleMenu } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'

function MyEditor() {
  const editor = useEditor({
    extensions: [StarterKit],
  })

  if (!editor) {
    return null
  }

  return (
    <>
      <EditorContent editor={editor} />
      <BubbleMenu editor={editor} className="bubble-menu">
        <button
          onClick={() => editor.chain().focus().toggleBold().run()}
          className={editor.isActive('bold') ? 'active' : ''}
        >
          Bold
        </button>
        <button
          onClick={() => editor.chain().focus().toggleItalic().run()}
          className={editor.isActive('italic') ? 'active' : ''}
        >
          Italic
        </button>
        <button
          onClick={() => editor.chain().focus().toggleStrike().run()}
          className={editor.isActive('strike') ? 'active' : ''}
        >
          Strike
        </button>
      </BubbleMenu>
    </>
  )
}
```

### With Custom Positioning

```tsx theme={null}
import { useEditor, EditorContent, BubbleMenu } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'

function MyEditor() {
  const editor = useEditor({
    extensions: [StarterKit],
  })

  return (
    <>
      <EditorContent editor={editor} />
      <BubbleMenu
        editor={editor}
        options={{
          placement: 'bottom',
          offset: 16,
          flip: { padding: 8 },
          shift: { padding: 8 },
        }}
      >
        <button onClick={() => editor?.chain().focus().toggleBold().run()}>
          Bold
        </button>
      </BubbleMenu>
    </>
  )
}
```

## Styling

### CSS Example

```css theme={null}
.bubble-menu {
  display: flex;
  gap: 4px;
  padding: 8px;
  background: white;
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

.bubble-menu button {
  padding: 6px 12px;
  border: none;
  background: transparent;
  border-radius: 4px;
  cursor: pointer;
  transition: background 0.2s;
}

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

.bubble-menu button.active {
  background: #3b82f6;
  color: white;
}
```

## Important Notes

<Note>
  The BubbleMenu uses Floating UI for positioning, which provides intelligent placement that avoids overflow and adjusts to viewport constraints.
</Note>

<Warning>
  When clicking on buttons inside the bubble menu, make sure to use `mousedown` or include `.focus()` in your command chains to prevent the editor from losing focus.
</Warning>

<Tip>
  Use different `pluginKey` values when rendering multiple bubble menus to avoid conflicts. Each menu needs a unique identifier.
</Tip>

## Default Behavior

By default, the bubble menu:

* Shows when text is selected
* Hides when the selection is empty
* Hides when the editor loses focus (unless focus moves to an element inside the menu)
* Positions itself above the selection
* Automatically adjusts position when the viewport is too small

## Related

* [FloatingMenu](/api/react/floating-menu) - Menu that appears in empty lines
* [useEditor](/api/react/use-editor) - Hook for creating editor instances
* [Floating UI Documentation](https://floating-ui.com/docs/computePosition) - Advanced positioning options
