Documentation Index Fetch the complete documentation index at: https://mintlify.com/ueberdosis/tiptap/llms.txt
Use this file to discover all available pages before exploring further.
Tiptap provides first-class support for React with hooks, components, and utilities that integrate seamlessly with React’s ecosystem.
Installation
Install the packages
Install the React package along with the core package and any extensions you need: npm install @tiptap/react @tiptap/core @tiptap/starter-kit
Import and use
Import the necessary components and hooks in your React component: import { useEditor , EditorContent } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
Core Concepts
useEditor Hook
The useEditor hook is the primary way to create and manage an editor instance in React. It handles the editor lifecycle automatically.
Type Signature
function useEditor (
options : UseEditorOptions ,
deps ?: DependencyList
) : Editor | null
interface UseEditorOptions extends Partial < EditorOptions > {
/**
* Whether to render the editor on the first render.
* If client-side rendering, set this to `true`.
* If server-side rendering, set this to `false`.
* @default true
*/
immediatelyRender ?: boolean
/**
* Whether to re-render the editor on each transaction.
* This is legacy behavior that will be removed in future versions.
* @default false
*/
shouldRerenderOnTransaction ?: boolean
}
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 } />
}
SSR Configuration
For server-side rendering (Next.js, Remix, etc.), you must set immediatelyRender to false:
function MyEditor () {
const editor = useEditor ({
immediatelyRender: false , // Required for SSR
extensions: [ StarterKit ],
content: '<p>Hello World!</p>' ,
})
return < EditorContent editor = { editor } />
}
For better performance, disable transaction re-renders and use useEditorState instead:
import { useEditor , useEditorState , EditorContent } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
function MyEditor () {
const editor = useEditor ({
immediatelyRender: true ,
shouldRerenderOnTransaction: false , // Disable automatic re-renders
extensions: [ StarterKit ],
content: '<p>Hello World!</p>' ,
})
// Select only the state you need
const { isBold , isItalic } = useEditorState ({
editor ,
selector : ( ctx ) => ({
isBold: ctx . editor . isActive ( 'bold' ),
isItalic: ctx . editor . isActive ( 'italic' ),
}),
})
return (
< div >
< button onClick = { () => editor ?. chain (). focus (). toggleBold (). run () } >
Bold { isBold ? '(active)' : '' }
</ button >
< button onClick = { () => editor ?. chain (). focus (). toggleItalic (). run () } >
Italic { isItalic ? '(active)' : '' }
</ button >
< EditorContent editor = { editor } />
</ div >
)
}
EditorContent Component
The EditorContent component renders the actual editor interface.
import type { Editor } from '@tiptap/core'
import { EditorContent } from '@tiptap/react'
interface EditorContentProps {
editor : Editor | null
className ?: string
style ?: React . CSSProperties
// ... other HTML div props
}
Usage Example
function MyEditor () {
const editor = useEditor ({
extensions: [ StarterKit ],
content: '<p>Hello World!</p>' ,
})
return (
< EditorContent
editor = { editor }
className = "prose max-w-none"
style = { { minHeight: '200px' } }
/>
)
}
New Tiptap Component API
Tiptap 3.18+ introduces a new component-based API that provides a cleaner, more React-idiomatic way to build editors.
Tiptap Provider Component
The Tiptap component provides the editor instance via context, making it available to all child components.
import { Tiptap , useEditor , useTiptap } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
function Toolbar () {
// Access editor from context
const { editor } = useTiptap ()
return (
< div >
< button onClick = { () => editor . chain (). focus (). toggleBold (). run () } >
Bold
</ button >
< button onClick = { () => editor . chain (). focus (). toggleItalic (). run () } >
Italic
</ button >
</ div >
)
}
function App () {
const editor = useEditor ({
extensions: [ StarterKit ],
content: '<p>Hello World!</p>'
})
return (
< Tiptap editor = { editor } >
< Toolbar />
< Tiptap.Content className = "editor" />
</ Tiptap >
)
}
useTiptap Hook
Access the editor instance from any component inside the Tiptap provider:
import { useTiptap } from '@tiptap/react'
function WordCount () {
const { editor } = useTiptap ()
const count = editor . state . doc . textContent . split ( / \s + / ). filter ( Boolean ). length
return < span > { count } words </ span >
}
useTiptapState Hook
Select specific editor state slices for optimized re-renders:
import { useTiptapState } from '@tiptap/react'
function FormattingButtons () {
const state = useTiptapState (
( ctx ) => ({
isBold: ctx . editor . isActive ( 'bold' ),
isItalic: ctx . editor . isActive ( 'italic' ),
isStrike: ctx . editor . isActive ( 'strike' ),
}),
// Optional equality function
( a , b ) => a . isBold === b . isBold && a . isItalic === b . isItalic && a . isStrike === b . isStrike
)
return (
< div >
< button disabled = { ! state . isBold } > Bold </ button >
< button disabled = { ! state . isItalic } > Italic </ button >
< button disabled = { ! state . isStrike } > Strike </ button >
</ div >
)
}
Complete Working Example
Basic Editor
With Toolbar
New Tiptap Component API
import { useEditor , EditorContent } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
import './styles.css'
export default function Editor () {
const editor = useEditor ({
extensions: [ StarterKit ],
content: `
<h2>Hi there,</h2>
<p>this is a <em>basic</em> example of <strong>Tiptap</strong>.</p>
` ,
})
return < EditorContent editor = { editor } />
}
Event Handlers
Handle editor events through the useEditor options:
const editor = useEditor ({
extensions: [ StarterKit ],
content: '<p>Hello World!</p>' ,
onUpdate : ({ editor }) => {
console . log ( 'Content updated:' , editor . getHTML ())
},
onCreate : ({ editor }) => {
console . log ( 'Editor created' )
},
onFocus : ({ editor , event }) => {
console . log ( 'Editor focused' )
},
onBlur : ({ editor , event }) => {
console . log ( 'Editor blurred' )
},
onSelectionUpdate : ({ editor }) => {
console . log ( 'Selection updated' )
},
})
Dependencies Array
The second parameter of useEditor accepts a dependencies array, similar to useEffect. When dependencies change, the editor is recreated:
function MyEditor ({ userId }) {
const editor = useEditor ({
extensions: [ StarterKit ],
content: '<p>Hello World!</p>' ,
}, [ userId ]) // Recreate editor when userId changes
return < EditorContent editor = { editor } />
}
TypeScript Support
Tiptap has full TypeScript support with comprehensive type definitions:
import type { Editor } from '@tiptap/core'
import type { UseEditorOptions } from '@tiptap/react'
import { useEditor , EditorContent } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
const editorConfig : UseEditorOptions = {
extensions: [ StarterKit ],
content: '<p>Hello World!</p>' ,
}
function MyEditor () {
const editor : Editor | null = useEditor ( editorConfig )
return < EditorContent editor = { editor } />
}
Advanced: Custom Node Views
Create React components as custom node views:
import { NodeViewWrapper , NodeViewContent , ReactNodeViewRenderer } from '@tiptap/react'
import { Node } from '@tiptap/core'
// React component for the node
function Component ( props ) {
return (
< NodeViewWrapper className = "custom-node" >
< div > Custom node content: </ div >
< NodeViewContent />
</ NodeViewWrapper >
)
}
// Define the extension
const CustomNode = Node . create ({
name: 'customNode' ,
group: 'block' ,
content: 'inline*' ,
parseHTML () {
return [{ tag: 'div[data-type="custom-node"]' }]
},
renderHTML ({ HTMLAttributes }) {
return [ 'div' , { 'data-type' : 'custom-node' , ... HTMLAttributes }, 0 ]
},
addNodeView () {
return ReactNodeViewRenderer ( Component )
},
})
Bubble and floating menus are now separate imports to keep @floating-ui/dom optional:
import { BubbleMenu , FloatingMenu } from '@tiptap/react/menus'
import { useEditor , EditorContent } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
function Editor () {
const editor = useEditor ({
extensions: [ StarterKit ],
content: '<p>Hello World!</p>' ,
})
return (
<>
< EditorContent editor = { editor } />
< BubbleMenu editor = { editor } >
< button onClick = { () => editor ?. chain (). focus (). toggleBold (). run () } >
Bold
</ button >
</ BubbleMenu >
< FloatingMenu editor = { editor } >
< button onClick = { () => editor ?. chain (). focus (). toggleHeading ({ level: 1 }). run () } >
H1
</ button >
</ FloatingMenu >
</>
)
}
Next Steps
Extensions Explore available extensions to enhance your editor
Commands Learn about editor commands and chains
Styling Customize the editor’s appearance
Node Views Create interactive custom nodes