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
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}
/>
)
}
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