Drag Handle React extension

VersionDownloads

Have you ever wanted to drag nodes around your react-based editor? Well, we did too—so here's an extension for that.

The DragHandleReact component allows you to easily handle dragging nodes around in the editor. You can define custom render functions, placement, and more. It essentially wraps the DragHandle extension in a React component that will automatically register/unregister the extension with the editor.

Install

npm install @tiptap/extension-drag-handle-react @tiptap/extension-drag-handle @tiptap/extension-node-range @tiptap/extension-collaboration @tiptap/y-tiptap yjs y-protocols

Props

All props follow the same structure as the DragHandle extension.

children

The content that should be displayed inside of the drag handle.

<DragHandle>
  <div>Drag Me!</div>
</DragHandle>

computePositionConfig

Configuration for position computation of the drag handle using the floating-ui/dom package. You can pass any options that are available in the floating-ui documentation.

Default: { placement: 'left-start', strategy: 'absolute' }

<DragHandle
  computePositionConfig={{
    placement: 'left',
    strategy: 'fixed',
  }}
>
  <div>Drag Me!</div>
</DragHandle>

onNodeChange

Returns a node or null when a node is hovered over. This can be used to highlight the node that is currently hovered over.

Default: undefined

function Component() {
  const [selectedNode, setSelectedNode] = useState(null)

  return (
    <DragHandle
      onNodeChange={({ node, editor, pos }) => {
        setSelectedNode(node)
        // do something with the node
      }}
    >
      <div>Drag Me!</div>
    </DragHandle>
  )
}

getReferencedVirtualElement

A function that returns the virtual element for the drag handle. This is useful when the menu needs to be positioned relative to a specific DOM element.

Default: undefined

<DragHandle
  getReferencedVirtualElement={() => {
    // Return a virtual element for custom positioning
    return null
  }}
>
  <div>Drag Me!</div>
</DragHandle>

locked

Locks the draghandle in place and visibility. If the drag handle was visible, it will remain visible until unlocked. If it was hidden, it will remain hidden until unlocked.

Default: false

<DragHandle locked={true}>
  <div>Drag Me!</div>
</DragHandle>

pluginKey

The key that should be used to store the plugin in the editor. This is useful if you have multiple drag handles in the same editor.

Default: undefined

<DragHandle pluginKey="myCustomDragHandle">
  <div>Drag Me!</div>
</DragHandle>

onElementDragStart

A function that is called when the element starts to be dragged. This can be used to add custom logic when dragging starts.

<DragHandle
  onElementDragStart={(e: DragEvent) => {
    // do something when dragging starts
  }}
>
  <div>Drag Me!</div>
</DragHandle>

onElementDragEnd

A function that is called when the element stops being dragged. This can be used to add custom logic when dragging ends.

<DragHandle
  onElementDragEnd={(e: DragEvent) => {
    // do something when dragging ends
  }}
>
  <div>Drag Me!</div>
</DragHandle>

nested

Enable drag handles for nested content such as list items, blockquotes, and other nested structures.

When enabled, the drag handle will appear for nested blocks, not just top-level blocks. A rule-based scoring system determines which node to target based on cursor position and configured rules.

Default: false

// Enable with defaults
<DragHandle nested={true}>
  <div>Drag Me!</div>
</DragHandle>

// Enable with custom edge detection
<DragHandle nested={{ edgeDetection: { threshold: -16 } }}>
  <div>Drag Me!</div>
</DragHandle>

Full Example with Nested Support

import DragHandle from '@tiptap/extension-drag-handle-react'
import { EditorContent, useEditor } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
import { useState } from 'react'

const NESTED_CONFIG = { edgeDetection: { threshold: -16 } }

export default function Editor() {
  const [nested, setNested] = useState(true)

  const editor = useEditor({
    extensions: [StarterKit],
    content: '<ul><li>Item 1</li><li>Item 2</li></ul>',
  })

  return (
    <>
      <button onClick={() => setNested(!nested)}>
        Toggle nested
      </button>
      <DragHandle editor={editor} nested={nested ? NESTED_CONFIG : false}>
        <div className="drag-handle" />
      </DragHandle>
      <EditorContent editor={editor} />
    </>
  )
}

Performance Tip

When using React state to toggle the nested prop, define your configuration object as a constant outside the component to avoid unnecessary re-renders.

See the DragHandle nested documentation for detailed configuration options including edge detection, custom rules, and allowed containers.

Commands

See the DragHandle extension for available editor commands.