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

# FloatingMenu

> Display a menu that appears in empty lines, perfect for slash commands and block insertion

The FloatingMenu extension provides a menu that appears when the cursor is in an empty line at the root level. It's commonly used for slash commands, block insertion menus, or quick formatting options. The menu is positioned using Floating UI and supports custom visibility rules.

## Installation

<CodeGroup>
  ```bash npm theme={null}
  npm install @tiptap/extension-floating-menu
  ```

  ```bash yarn theme={null}
  yarn add @tiptap/extension-floating-menu
  ```

  ```bash pnpm theme={null}
  pnpm add @tiptap/extension-floating-menu
  ```
</CodeGroup>

## Basic Usage

### Vanilla JavaScript

```javascript theme={null}
import { Editor } from '@tiptap/core'
import FloatingMenu from '@tiptap/extension-floating-menu'

const menu = document.querySelector('#floating-menu')

const editor = new Editor({
  extensions: [
    FloatingMenu.configure({
      element: menu,
    }),
  ],
})
```

### React

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

function Editor() {
  const editor = useEditor({
    extensions: [StarterKit],
    content: '<p>Place cursor in an empty line to see the floating menu</p>',
  })

  return (
    <>
      <EditorContent editor={editor} />
      <FloatingMenu editor={editor}>
        <button onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}>
          H1
        </button>
        <button onClick={() => editor.chain().focus().toggleBulletList().run()}>
          Bullet List
        </button>
      </FloatingMenu>
    </>
  )
}
```

### Vue

```vue theme={null}
<template>
  <editor-content :editor="editor" />
  <floating-menu :editor="editor" v-if="editor">
    <button @click="editor.chain().focus().toggleHeading({ level: 1 }).run()">
      H1
    </button>
    <button @click="editor.chain().focus().toggleBulletList().run()">
      Bullet List
    </button>
  </floating-menu>
</template>

<script>
import { Editor, EditorContent } from '@tiptap/vue-3'
import { FloatingMenu } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'

export default {
  components: {
    EditorContent,
    FloatingMenu,
  },
  data() {
    return {
      editor: null,
    }
  },
  mounted() {
    this.editor = new Editor({
      extensions: [StarterKit],
      content: '<p>Place cursor in an empty line to see the floating menu</p>',
    })
  },
  beforeUnmount() {
    this.editor.destroy()
  },
}
</script>
```

## Configuration Options

<ParamField path="element" type="HTMLElement" required>
  The DOM element that contains your menu.

  **Default:** `null`
</ParamField>

<ParamField path="pluginKey" type="PluginKey | string">
  The plugin key for the floating menu.

  **Default:** `'floatingMenu'`
</ParamField>

<ParamField path="updateDelay" type="number">
  The delay in milliseconds before the menu position should be updated. This can be useful to prevent performance issues.

  **Default:** `250`
</ParamField>

<ParamField path="resizeDelay" type="number">
  The delay in milliseconds before the menu position should be updated on window resize.

  **Default:** `60`
</ParamField>

<ParamField path="shouldShow" type="function">
  A function that determines whether the menu should be shown or not. The default implementation shows the menu when the cursor is in an empty text block at the root level.

  **Parameters:**

  * `editor`: The editor instance
  * `view`: The ProseMirror EditorView
  * `state`: The current editor state
  * `oldState`: The previous editor state (optional)
  * `from`: Selection start position
  * `to`: Selection end position

  **Default behavior:**

  * Shows when editor has focus
  * Selection is empty (cursor, not range)
  * Cursor is at root depth (depth === 1)
  * Current block is empty text block
  * Editor is editable

  ```javascript theme={null}
  FloatingMenu.configure({
    element: menu,
    shouldShow: ({ editor, view, state }) => {
      const { selection } = state
      const { $anchor, empty } = selection
      const isRootDepth = $anchor.depth === 1
      const isEmptyTextBlock = 
        $anchor.parent.isTextblock && 
        !$anchor.parent.textContent
      
      return view.hasFocus() && empty && isRootDepth && isEmptyTextBlock
    },
  })
  ```
</ParamField>

<ParamField path="appendTo" type="HTMLElement | (() => HTMLElement)">
  The DOM element to append your menu to. Sometimes the menu needs to be appended to a different DOM context due to accessibility, clipping, or z-index issues.

  **Default:** Editor's parent element

  ```javascript theme={null}
  FloatingMenu.configure({
    element: menu,
    appendTo: () => document.body,
  })
  ```
</ParamField>

<ParamField path="options" type="object">
  Configuration options passed to Floating UI for positioning. See [Floating UI documentation](https://floating-ui.com/docs/computePosition#options) for full details.

  **Properties:**

  <ParamField path="options.strategy" type="'absolute' | 'fixed'">
    Positioning strategy.

    **Default:** `'absolute'`
  </ParamField>

  <ParamField path="options.placement" type="string">
    Menu placement relative to cursor. Options: `'top'`, `'right'`, `'bottom'`, `'left'`, `'top-start'`, `'top-end'`, `'right-start'`, `'right-end'`, `'bottom-start'`, `'bottom-end'`, `'left-start'`, `'left-end'`

    **Default:** `'right'`
  </ParamField>

  <ParamField path="options.offset" type="number | object | boolean">
    Distance in pixels from the cursor.

    **Default:** `8`
  </ParamField>

  <ParamField path="options.flip" type="object | boolean">
    Whether to flip the menu to the opposite side if there's not enough space.

    **Default:** `{}`
  </ParamField>

  <ParamField path="options.shift" type="object | boolean">
    Whether to shift the menu along the axis to keep it in view.

    **Default:** `{}`
  </ParamField>

  <ParamField path="options.scrollTarget" type="HTMLElement | Window">
    The scrollable element that should be listened to when updating the position.

    **Default:** `window`

    ```javascript theme={null}
    FloatingMenu.configure({
      element: menu,
      options: {
        scrollTarget: document.querySelector('.scroll-container'),
      },
    })
    ```
  </ParamField>

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

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

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

  <ParamField path="options.onDestroy" type="() => void">
    Callback fired when the menu is destroyed.
  </ParamField>
</ParamField>

## Commands

<ParamField path="updateFloatingMenuPosition" type="command">
  Manually update the position of the floating menu.

  ```javascript theme={null}
  editor.commands.updateFloatingMenuPosition()
  ```
</ParamField>

## React Component Props

When using the React component, you can pass these props:

<ParamField path="editor" type="Editor">
  The editor instance. Can be omitted if used inside `EditorProvider`.
</ParamField>

<ParamField path="pluginKey" type="string">
  The plugin key.

  **Default:** `'floatingMenu'`
</ParamField>

<ParamField path="updateDelay" type="number">
  Update delay in milliseconds.
</ParamField>

<ParamField path="resizeDelay" type="number">
  Resize delay in milliseconds.
</ParamField>

<ParamField path="shouldShow" type="function">
  Function to determine visibility.
</ParamField>

<ParamField path="options" type="object">
  Floating UI configuration options.
</ParamField>

Plus all standard HTML div attributes (className, style, etc.).

## Vue Component Props

<ParamField path="editor" type="Editor" required>
  The editor instance.
</ParamField>

<ParamField path="pluginKey" type="string">
  The plugin key.

  **Default:** `'floatingMenu'`
</ParamField>

<ParamField path="updateDelay" type="number">
  Update delay in milliseconds.
</ParamField>

<ParamField path="resizeDelay" type="number">
  Resize delay in milliseconds.
</ParamField>

<ParamField path="shouldShow" type="function">
  Function to determine visibility.
</ParamField>

<ParamField path="options" type="object">
  Floating UI configuration options.
</ParamField>

<ParamField path="appendTo" type="HTMLElement | Function">
  Element to append the menu to.
</ParamField>

## Advanced Examples

### Slash Command Menu

```tsx theme={null}
import { FloatingMenu } from '@tiptap/react'
import { useState } from 'react'

function Editor() {
  const [query, setQuery] = useState('')
  
  const commands = [
    { name: 'Heading 1', command: () => editor.chain().focus().toggleHeading({ level: 1 }).run() },
    { name: 'Heading 2', command: () => editor.chain().focus().toggleHeading({ level: 2 }).run() },
    { name: 'Bullet List', command: () => editor.chain().focus().toggleBulletList().run() },
    { name: 'Numbered List', command: () => editor.chain().focus().toggleOrderedList().run() },
  ]
  
  const filteredCommands = commands.filter(cmd => 
    cmd.name.toLowerCase().includes(query.toLowerCase())
  )
  
  return (
    <>
      <EditorContent editor={editor} />
      <FloatingMenu 
        editor={editor}
        shouldShow={({ state }) => {
          const { $anchor } = state.selection
          const text = $anchor.parent.textContent
          return text.startsWith('/')
        }}
      >
        <div className="slash-menu">
          {filteredCommands.map(cmd => (
            <button key={cmd.name} onClick={cmd.command}>
              {cmd.name}
            </button>
          ))}
        </div>
      </FloatingMenu>
    </>
  )
}
```

### Show in Nested Blocks

```javascript theme={null}
FloatingMenu.configure({
  element: menu,
  shouldShow: ({ view, state }) => {
    const { selection } = state
    const { $anchor, empty } = selection
    
    // Allow any depth, not just root level
    const isEmptyTextBlock = 
      $anchor.parent.isTextblock && 
      !$anchor.parent.textContent
    
    return view.hasFocus() && empty && isEmptyTextBlock
  },
})
```

### Update Position Programmatically

```javascript theme={null}
// Trigger a position update
editor.commands.updateFloatingMenuPosition()

// Or via transaction meta
editor.view.dispatch(
  editor.state.tr.setMeta('floatingMenu', 'updatePosition')
)
```

### Custom Positioning

```javascript theme={null}
FloatingMenu.configure({
  element: menu,
  options: {
    placement: 'left',
    offset: 16,
    flip: {
      fallbackPlacements: ['right', 'top', 'bottom'],
    },
  },
})
```

### Multiple Floating Menus

```tsx theme={null}
function Editor() {
  const editor = useEditor({
    extensions: [StarterKit],
  })

  return (
    <>
      <EditorContent editor={editor} />
      
      {/* Block insertion menu */}
      <FloatingMenu
        editor={editor}
        pluginKey="blockMenu"
      >
        <button onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}>
          H1
        </button>
        <button onClick={() => editor.chain().focus().toggleBulletList().run()}>
          List
        </button>
      </FloatingMenu>
      
      {/* AI writing assistant */}
      <FloatingMenu
        editor={editor}
        pluginKey="aiMenu"
        options={{ placement: 'left' }}
      >
        <button onClick={() => improveWriting()}>
          ✨ Improve Writing
        </button>
      </FloatingMenu>
    </>
  )
}
```

## Source Code

View the source code on GitHub:

* [Extension](https://github.com/ueberdosis/tiptap/tree/main/packages/extension-floating-menu/src/floating-menu.ts:31)
* [Plugin](https://github.com/ueberdosis/tiptap/tree/main/packages/extension-floating-menu/src/floating-menu-plugin.ts:131)
* [React Component](https://github.com/ueberdosis/tiptap/tree/main/packages/react/src/menus/FloatingMenu.tsx:14)
* [Vue Component](https://github.com/ueberdosis/tiptap/tree/main/packages/vue-3/src/menus/FloatingMenu.ts:6)
