Find out what's new in Tiptap Editor 3.0

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

NameTypeDefaultDescription
editorEditor | nullundefinedThe Tiptap editor instance to attach to
shouldShowbooleanundefinedControls whether the floating element should be visible
floatingOptionsPartial<UseFloatingOptions>undefinedAdditional options to pass to the floating UI
zIndexnumber50Z-index for the floating element
onOpenChange(open: boolean) => voidundefinedCallback fired when the visibility state changes
onRectChange(rect: DOMRect | null) => voidundefinedCallback fired when the selection rectangle changes
getBoundingClientRect(editor: Editor) => DOMRect | nullgetSelectionBoundingRectCustom function to determine the position of the floating element
updateOnScrollbooleantrueWhether to update position on scroll events
closeOnEscapebooleantrueWhether to close the floating element when Escape key is pressed
childrenReact.ReactNodeundefinedContent 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)