Skip to main content

Overview

The EditorContent component is responsible for rendering the actual editor interface where users can interact with content. It must be used in conjunction with the useEditor hook.

Type Signature

const EditorContent: React.ForwardRefExoticComponent<
  EditorContentProps & React.RefAttributes<HTMLDivElement>
>

interface EditorContentProps extends HTMLProps<HTMLDivElement> {
  editor: Editor | null
  innerRef?: ForwardedRef<HTMLDivElement | null>
}

Props

editor
Editor | null
required
The editor instance created by the useEditor hook. Can be null during initialization.
...props
HTMLProps<HTMLDivElement>
All standard HTML div attributes are supported and will be passed to the wrapper element. This includes:
  • className - CSS class names
  • style - Inline styles
  • id - Element ID
  • onClick, onFocus, etc. - Event handlers
  • Any other valid div attributes

Ref Forwarding

The component supports ref forwarding, allowing you to get a reference to the underlying HTMLDivElement:
import { useRef } from 'react'
import { useEditor, EditorContent } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'

function MyEditor() {
  const editorRef = useRef<HTMLDivElement>(null)
  const editor = useEditor({
    extensions: [StarterKit],
  })

  return <EditorContent ref={editorRef} editor={editor} />
}

Usage Examples

Basic Usage

import { useEditor, EditorContent } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'

function MyEditor() {
  const editor = useEditor({
    extensions: [StarterKit],
    content: '<p>Hello World!</p>',
  })

  return <EditorContent editor={editor} />
}

With Custom Styling

import { useEditor, EditorContent } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'

function MyEditor() {
  const editor = useEditor({
    extensions: [StarterKit],
    content: '<p>Hello World!</p>',
  })

  return (
    <EditorContent
      editor={editor}
      className="prose max-w-none"
      style={{
        minHeight: '200px',
        border: '1px solid #ccc',
        borderRadius: '8px',
        padding: '16px',
      }}
    />
  )
}

With Loading State

import { useEditor, EditorContent } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'

function MyEditor() {
  const editor = useEditor({
    extensions: [StarterKit],
    content: '<p>Hello World!</p>',
    immediatelyRender: false,
  })

  if (!editor) {
    return (
      <div className="editor-loading">
        <p>Loading editor...</p>
      </div>
    )
  }

  return <EditorContent editor={editor} />
}

Multiple Editors

import { useEditor, EditorContent } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'

function MyEditor() {
  const editor1 = useEditor({
    extensions: [StarterKit],
    content: '<p>Editor 1</p>',
  })

  const editor2 = useEditor({
    extensions: [StarterKit],
    content: '<p>Editor 2</p>',
  })

  return (
    <div>
      <EditorContent editor={editor1} className="editor-1" />
      <EditorContent editor={editor2} className="editor-2" />
    </div>
  )
}

With Click Handler

import { useEditor, EditorContent } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'

function MyEditor() {
  const editor = useEditor({
    extensions: [StarterKit],
    content: '<p>Click on me!</p>',
  })

  const handleClick = (event: React.MouseEvent<HTMLDivElement>) => {
    console.log('Editor clicked', event)
  }

  return (
    <EditorContent
      editor={editor}
      onClick={handleClick}
    />
  )
}

Combining with Toolbar

import { useEditor, EditorContent } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'

function MyEditor() {
  const editor = useEditor({
    extensions: [StarterKit],
    content: '<p>Hello World!</p>',
  })

  if (!editor) {
    return null
  }

  return (
    <div className="editor-container">
      <div className="toolbar">
        <button onClick={() => editor.chain().focus().toggleBold().run()}>
          Bold
        </button>
        <button onClick={() => editor.chain().focus().toggleItalic().run()}>
          Italic
        </button>
      </div>
      <EditorContent
        editor={editor}
        className="editor-content"
      />
    </div>
  )
}

Styling

CSS Classes

The editor content is wrapped in a div that you can style. The actual ProseMirror editor will be nested inside:
/* Your wrapper div */
.my-editor {
  border: 1px solid #ccc;
  border-radius: 8px;
}

/* ProseMirror editor */
.my-editor .ProseMirror {
  padding: 16px;
  min-height: 200px;
  outline: none;
}

/* Focus styles */
.my-editor .ProseMirror:focus {
  outline: none;
}

/* Content styles */
.my-editor .ProseMirror p {
  margin: 0 0 1em;
}

.my-editor .ProseMirror h1 {
  font-size: 2em;
  font-weight: bold;
}

Tailwind CSS

<EditorContent
  editor={editor}
  className="
    prose
    max-w-none
    rounded-lg
    border
    border-gray-300
    p-4
    focus-within:border-blue-500
    focus-within:ring-2
    focus-within:ring-blue-200
  "
/>

Important Notes

The EditorContent component uses React portals internally to render node views. This ensures that React components used in custom node views maintain their context and state properly.
Do not conditionally render EditorContent based on the editor instance. Instead, handle the loading state by checking if the editor is null before rendering your component, or show a loading indicator.
You can render multiple EditorContent components with the same editor instance, but this is generally not recommended as it can lead to unexpected behavior. Each editor instance should typically have one content component.

Behavior

Mounting and Unmounting

  • When mounted, EditorContent attaches the ProseMirror view to the DOM
  • When unmounted, it properly cleans up the view and removes event listeners
  • The editor instance itself is not destroyed when EditorContent unmounts

Node Views

EditorContent automatically manages React-based node views:
  • Node views are rendered using React portals
  • They maintain full React context (hooks, context API, etc.)
  • Updates are synchronized with the editor state

Build docs developers (and LLMs) love