Floating Element
A floating UI element that positions itself relative to the current selection in a Tiptap editor. Used for floating toolbars, menus, and other UI elements that need to appear near the text cursor with intelligent positioning and interaction handling.
Installation
Add the component via the Tiptap CLI:
npx @tiptap/cli@latest add floating-element
Components
<FloatingElement />
A versatile React component that creates floating UI elements positioned relative to text selections in Tiptap editors.
Usage
import * as React from 'react'
import { EditorContent, EditorContext, useEditor } from '@tiptap/react'
// --- Tiptap Core Extensions ---
import { StarterKit } from '@tiptap/starter-kit'
// --- Tiptap UI ---
import { FloatingElement } from '@/components/tiptap-ui-utils/floating-element'
import { MarkButton } from '@/components/tiptap-ui/mark-button'
// --- UI Primitives ---
import { ButtonGroup } from '@/components/tiptap-ui-primitive/button'
import { Toolbar } from '@/components/tiptap-ui-primitive/toolbar'
// --- Tiptap Node ---
import '@/components/tiptap-node/paragraph-node/paragraph-node.scss'
export const FloatingElementExample = () => {
const editor = useEditor({
immediatelyRender: false,
content: `<h2>Floating Element Example</h2>
<p>Try selecting some text in this editor. A simple formatting toolbar will appear above your selection.
The FloatingElement component positions UI elements relative to the text selection or cursor position.
It's commonly used for contextual toolbars, menus, and other elements that should appear near the current editing context.</p>`,
extensions: [StarterKit],
})
return (
<EditorContext.Provider value={{ editor }}>
<EditorContent editor={editor} role="presentation" className="control-showcase" />
<FloatingElement editor={editor}>
<Toolbar variant="floating">
<ButtonGroup orientation="horizontal">
<MarkButton type="bold" />
<MarkButton type="italic" />
</ButtonGroup>
</Toolbar>
</FloatingElement>
</EditorContext.Provider>
)
}
Props
Name | Type | Default | Description |
---|---|---|---|
editor | Editor | null | undefined | The Tiptap editor instance to attach to |
shouldShow | boolean | undefined | Controls whether the floating element should be visible |
floatingOptions | Partial<UseFloatingOptions> | undefined | Additional options to pass to the floating UI |
zIndex | number | 50 | Z-index for the floating element |
onOpenChange | (open: boolean) => void | undefined | Callback fired when the visibility state changes |
onRectChange | (rect: DOMRect | null) => void | undefined | Callback fired when the selection rectangle changes |
getBoundingClientRect | (editor: Editor) => DOMRect | null | getSelectionBoundingRect | Custom function to determine the position of the floating element |
updateOnScroll | boolean | true | Whether to update position on scroll events |
closeOnEscape | boolean | true | Whether to close the floating element when Escape key is pressed |
children | React.ReactNode | undefined | Content to display inside the floating element |
Advanced Usage Examples
Basic Floating Toolbar
function FloatingToolbar() {
return (
<FloatingElement
editor={editor}
floatingOptions={{
placement: 'top',
middleware: [shift(), flip(), offset(8)],
}}
>
{/* Floating content here */}
</FloatingElement>
)
}
Custom Positioning with Mobile Support
function ResponsiveFloatingMenu() {
const isMobile = useMobile()
return (
<FloatingElement
editor={editor}
shouldShow={isMenuVisible}
getBoundingClientRect={(editor) => {
// Custom positioning logic
return getCustomRect(editor)
}}
{...(isMobile
? {
style: {
position: 'fixed',
left: 0,
right: 0,
bottom: 0,
margin: '.5rem',
zIndex: 50,
},
}
: {})}
>
{/* Floating content here */}
</FloatingElement>
)
}
Customize shouldShow
Floating Menu
function SelectionMenu() {
const [isVisible, setIsVisible] = useState(false)
useEffect(() => {
if (!editor) return
const updateVisibility = () => {
const hasSelection = !editor.state.selection.empty
const isValidSelection = isSelectionValid(editor)
setIsVisible(hasSelection && isValidSelection)
}
editor.on('selectionUpdate', updateVisibility)
return () => editor.off('selectionUpdate', updateVisibility)
}, [editor])
return (
<FloatingElement
editor={editor}
shouldShow={isVisible}
onRectChange={(rect) => {
console.log('Selection rect:', rect)
}}
>
{/* Your floating content here */}
</FloatingElement>
)
}
Utilities
getSelectionBoundingRect(editor)
Gets the bounding rectangle of the current selection in the editor.
import { getSelectionBoundingRect } from '@/registry/lib/tiptap-collab-utils'
const rect = getSelectionBoundingRect(editor)
console.log('Selection bounds:', rect)
isSelectionValid(editor)
Checks if the current selection is valid for showing floating elements.
import { isSelectionValid } from '@/registry/lib/tiptap-collab-utils'
const shouldShow = isSelectionValid(editor)