Overview
The FloatingMenu component renders a menu that appears when the cursor is on an empty line in the editor. Itβs commonly used to provide quick access to block-level formatting options like headings, lists, or inserting media.
Type Signature
const FloatingMenu : React . ForwardRefExoticComponent <
FloatingMenuProps & React . RefAttributes < HTMLDivElement >
>
type FloatingMenuProps = Omit < FloatingMenuPluginProps , 'element' | 'editor' > & {
editor : Editor | null
options ?: FloatingMenuPluginProps [ 'options' ]
} & React . HTMLAttributes < HTMLDivElement >
The editor instance. If not provided, the component will attempt to use the editor from React context via useCurrentEditor.
pluginKey
string | PluginKey
default: "'floatingMenu'"
Unique identifier for this floating menu plugin. Use different keys if you have multiple floating menus.
Delay in milliseconds before the menu position is updated. This debounces position updates for better performance.
Delay in milliseconds before the menu position is updated on window resize. This debounces resize events for better performance.
shouldShow
function | null
default: "null"
Function that determines whether the menu should be shown. Receives editor state and selection information. shouldShow ?: ( props : {
editor : Editor
view : EditorView
state : EditorState
oldState ?: EditorState
from : number
to : number
}) => boolean
Default behavior: Shows when the cursor is in an empty text block at the root level and the editor has focus.
appendTo
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.
Floating UI configuration options for positioning and behavior. strategy
'absolute' | 'fixed'
default: "'absolute'"
Positioning strategy for the menu.
placement
Placement
default: "'right'"
Preferred placement of the menu relative to the cursor. Options: 'top', 'right', 'bottom', 'left', or any -start/-end variant.
offset
number | object | boolean
default: "8"
Offset from the reference element in pixels. Can be a number, detailed offset configuration, or false to disable.
flip
object | boolean
default: "{}"
Enable flipping to the opposite placement if thereβs not enough space. Pass an object for configuration or false to disable.
shift
object | boolean
default: "{}"
Enable shifting along the axis to keep the menu in view. Pass an object for configuration or false to disable.
arrow
object | false
default: "false"
Configuration for an arrow element pointing to the reference. Pass an object with element reference or false to disable.
size
object | boolean
default: "false"
Enable size adjustments to fit the menu within the viewport. Pass an object for configuration or false to disable.
autoPlacement
object | boolean
default: "false"
Automatically choose the placement with the most space. Pass an object for configuration or false to disable.
hide
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.
inline
object | boolean
default: "false"
Improve positioning for inline reference elements. Pass an object for configuration or false to disable.
scrollTarget
HTMLElement | Window
default: "window"
The scrollable element to listen to for scroll events. The menu position will update when this element is scrolled.
Callback function called when the menu is shown.
Callback function called when the menu is hidden.
Callback function called when the menu position is updated.
Callback function called when the menu is destroyed.
The content to render inside the floating menu.
...props
React.HTMLAttributes<HTMLDivElement>
All standard HTML div attributes (className, style, etc.) are supported.
Usage Examples
Basic Usage
import { useEditor , EditorContent , FloatingMenu } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
function MyEditor () {
const editor = useEditor ({
extensions: [ StarterKit ],
content: '<p>Click on 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 () } >
List
</ button >
</ FloatingMenu >
</>
)
}
Block Type Selector
import { useEditor , EditorContent , FloatingMenu } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
function MyEditor () {
const editor = useEditor ({
extensions: [ StarterKit ],
})
return (
<>
< EditorContent editor = { editor } />
< FloatingMenu
editor = { editor }
className = "floating-menu"
>
< button
onClick = { () => editor ?. chain (). focus (). toggleHeading ({ level: 1 }). run () }
>
Heading 1
</ button >
< button
onClick = { () => editor ?. chain (). focus (). toggleHeading ({ level: 2 }). run () }
>
Heading 2
</ button >
< button
onClick = { () => editor ?. chain (). focus (). toggleBulletList (). run () }
>
Bullet List
</ button >
< button
onClick = { () => editor ?. chain (). focus (). toggleOrderedList (). run () }
>
Numbered List
</ button >
< button
onClick = { () => editor ?. chain (). focus (). toggleCodeBlock (). run () }
>
Code Block
</ button >
</ FloatingMenu >
</>
)
}
import { useEditor , EditorContent , FloatingMenu } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
function MyEditor () {
const editor = useEditor ({
extensions: [ StarterKit ],
})
return (
<>
< EditorContent editor = { editor } />
< FloatingMenu
editor = { editor }
style = { {
background: '#1a1a1a' ,
color: 'white' ,
padding: '8px' ,
borderRadius: '8px' ,
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.2)' ,
} }
>
< div style = { { display: 'flex' , gap: '4px' } } >
< button
style = { {
padding: '8px 12px' ,
background: 'transparent' ,
border: '1px solid #444' ,
color: 'white' ,
borderRadius: '4px' ,
cursor: 'pointer' ,
} }
onClick = { () => editor ?. chain (). focus (). toggleHeading ({ level: 1 }). run () }
>
H1
</ button >
< button
style = { {
padding: '8px 12px' ,
background: 'transparent' ,
border: '1px solid #444' ,
color: 'white' ,
borderRadius: '4px' ,
cursor: 'pointer' ,
} }
onClick = { () => editor ?. chain (). focus (). toggleBulletList (). run () }
>
List
</ button >
</ div >
</ FloatingMenu >
</>
)
}
Custom shouldShow Logic
import { useEditor , EditorContent , FloatingMenu } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
function MyEditor () {
const editor = useEditor ({
extensions: [ StarterKit ],
})
return (
<>
< EditorContent editor = { editor } />
< FloatingMenu
editor = { editor }
shouldShow = { ({ state , editor }) => {
const { $anchor } = state . selection
// Only show in paragraph nodes
if ( $anchor . parent . type . name !== 'paragraph' ) {
return false
}
// Only show when empty and at root level
return (
$anchor . parent . childCount === 0 &&
$anchor . depth === 1 &&
editor . isEditable
)
} }
>
< button onClick = { () => editor ?. chain (). focus (). toggleHeading ({ level: 1 }). run () } >
H1
</ button >
</ FloatingMenu >
</>
)
}
Slash Command Style
import { useEditor , EditorContent , FloatingMenu } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
import { useState } from 'react'
function MyEditor () {
const [ showSlashMenu , setShowSlashMenu ] = useState ( false )
const editor = useEditor ({
extensions: [ StarterKit ],
onUpdate : ({ editor }) => {
const { $from } = editor . state . selection
const text = $from . parent . textContent
setShowSlashMenu ( text === '/' )
},
})
const insertBlock = ( type : string ) => {
if ( ! editor ) return
// Delete the slash
editor . chain (). focus (). deleteRange ({ from: editor . state . selection . from - 1 , to: editor . state . selection . from }). run ()
// Insert the block
switch ( type ) {
case 'h1' :
editor . chain (). toggleHeading ({ level: 1 }). run ()
break
case 'list' :
editor . chain (). toggleBulletList (). run ()
break
case 'code' :
editor . chain (). toggleCodeBlock (). run ()
break
}
}
return (
<>
< EditorContent editor = { editor } />
{ showSlashMenu && (
< FloatingMenu
editor = { editor }
options = { { placement: 'bottom-start' } }
>
< div className = "slash-menu" >
< button onClick = { () => insertBlock ( 'h1' ) } > Heading 1 </ button >
< button onClick = { () => insertBlock ( 'list' ) } > Bullet List </ button >
< button onClick = { () => insertBlock ( 'code' ) } > Code Block </ button >
</ div >
</ FloatingMenu >
) }
</>
)
}
With Icons
import { useEditor , EditorContent , FloatingMenu } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
function MyEditor () {
const editor = useEditor ({
extensions: [ StarterKit ],
})
return (
<>
< EditorContent editor = { editor } />
< FloatingMenu editor = { editor } className = "floating-menu" >
< button
onClick = { () => editor ?. chain (). focus (). toggleHeading ({ level: 1 }). run () }
title = "Heading 1"
>
< svg width = "16" height = "16" viewBox = "0 0 16 16" >
< path d = "M2 3h2v10H2V3zm4 0h2v10H6V3zm6 0h2v10h-2V3z" />
</ svg >
</ button >
< button
onClick = { () => editor ?. chain (). focus (). toggleBulletList (). run () }
title = "Bullet List"
>
< svg width = "16" height = "16" viewBox = "0 0 16 16" >
< circle cx = "2" cy = "4" r = "1" />
< circle cx = "2" cy = "8" r = "1" />
< circle cx = "2" cy = "12" r = "1" />
< line x1 = "5" y1 = "4" x2 = "14" y2 = "4" />
< line x1 = "5" y1 = "8" x2 = "14" y2 = "8" />
< line x1 = "5" y1 = "12" x2 = "14" y2 = "12" />
</ svg >
</ button >
</ FloatingMenu >
</>
)
}
Custom Positioning
import { useEditor , EditorContent , FloatingMenu } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
function MyEditor () {
const editor = useEditor ({
extensions: [ StarterKit ],
})
return (
<>
< EditorContent editor = { editor } />
< FloatingMenu
editor = { editor }
options = { {
placement: 'left-start' ,
offset: 12 ,
shift: { padding: 8 },
onShow : () => console . log ( 'Menu shown' ),
onHide : () => console . log ( 'Menu hidden' ),
} }
>
< div > + Add block </ div >
</ FloatingMenu >
</>
)
}
Styling
CSS Example
.floating-menu {
display : flex ;
flex-direction : column ;
gap : 2 px ;
padding : 4 px ;
background : white ;
border : 1 px solid #e2e8f0 ;
border-radius : 8 px ;
box-shadow : 0 2 px 8 px rgba ( 0 , 0 , 0 , 0.1 );
}
.floating-menu button {
padding : 8 px 12 px ;
border : none ;
background : transparent ;
text-align : left ;
cursor : pointer ;
border-radius : 4 px ;
transition : background 0.2 s ;
}
.floating-menu button :hover {
background : #f1f5f9 ;
}
.floating-menu button :active {
background : #e2e8f0 ;
}
.floating-menu {
width : 32 px ;
height : 32 px ;
display : flex ;
align-items : center ;
justify-content : center ;
background : white ;
border : 1 px solid #e2e8f0 ;
border-radius : 4 px ;
cursor : pointer ;
transition : all 0.2 s ;
}
.floating-menu:hover {
background : #f1f5f9 ;
box-shadow : 0 2 px 4 px rgba ( 0 , 0 , 0 , 0.1 );
}
.floating-menu::before {
content : '+' ;
font-size : 18 px ;
font-weight : bold ;
color : #64748b ;
}
Important Notes
The FloatingMenu uses Floating UI for positioning, which provides intelligent placement that avoids overflow and adjusts to viewport constraints.
When clicking buttons inside the floating menu, make sure to use .focus() in your command chains to prevent the editor from losing focus, which would hide the menu.
The default shouldShow behavior only shows the menu on empty text blocks at the root level. Customize this behavior with the shouldShow prop to match your use case.
Default Behavior
By default, the floating menu:
Shows when the cursor is in an empty text block at root depth
Hides when text is typed
Hides when the editor loses focus
Positions itself to the right of the cursor
Only appears when the editor is editable
The menu will NOT show:
In empty list items
In code blocks
In nested content
When text exists in the block
When the editor doesnβt have focus
Feature FloatingMenu BubbleMenu Appears on Empty lines Text selection Default position Right of cursor Above selection Common use Block formatting Text formatting Example actions Insert heading, list, media Bold, italic, link