Tiptap Rich Text Editor
Status: Production Ready Last Updated: 2026-01-21 Dependencies: React 19+, Tailwind v4, shadcn/ui (recommended) Latest Versions: @tiptap/react@3.16.0, @tiptap/starter-kit@3.16.0, @tiptap/pm@3.16.0 (verified 2026-01-21)
Quick Start (5 Minutes)
1. Install Dependencies
bash1npm install @tiptap/react @tiptap/starter-kit @tiptap/pm @tiptap/extension-image @tiptap/extension-color @tiptap/extension-text-style @tiptap/extension-typography
Why this matters:
@tiptap/pmis required peer dependency (ProseMirror engine)- StarterKit bundles 20+ essential extensions (headings, lists, bold, italic, etc.)
- Image/color/typography are common additions not in StarterKit
Important: If using Tiptap v3.14.0+, drag handle functionality requires minimum v3.14.0 (regression fixed in that release). For Pro extensions with drag handles, React 18 is recommended due to tippyjs-react dependency.
2. Create SSR-Safe Editor
typescript1import { useEditor, EditorContent } from '@tiptap/react' 2import StarterKit from '@tiptap/starter-kit' 3 4export function Editor() { 5 const editor = useEditor({ 6 extensions: [StarterKit], 7 content: '<p>Hello World!</p>', 8 immediatelyRender: false, // ⚠️ CRITICAL for SSR/Next.js 9 editorProps: { 10 attributes: { 11 class: 'prose prose-sm focus:outline-none min-h-[200px] p-4', 12 }, 13 }, 14 }) 15 16 return <EditorContent editor={editor} /> 17}
CRITICAL:
- Always set
immediatelyRender: falsefor Next.js/SSR apps (prevents hydration mismatch) - Without this, you'll see: "SSR has been detected, please set
immediatelyRenderexplicitly tofalse" - This is the #1 error reported by Tiptap users
3. Add Tailwind Typography (Optional but Recommended)
bash1npm install @tailwindcss/typography
Update your tailwind.config.ts:
typescript1import typography from '@tailwindcss/typography' 2 3export default { 4 plugins: [typography], 5}
Why this matters:
- Provides default prose styling for headings, lists, links, etc.
- Without it, formatted content looks unstyled
- Alternative: Use custom Tailwind classes with
.tiptapselector
The 3-Step Setup Process
Step 1: Choose Your Integration Method
Option A: shadcn Minimal Tiptap Component (Recommended)
Install the pre-built shadcn component:
bash1npx shadcn@latest add https://raw.githubusercontent.com/Aslam97/shadcn-minimal-tiptap/main/registry/block-registry.json
This installs:
- Fully-featured editor component with toolbar
- Image upload support
- Code block with syntax highlighting
- Typography extension configured
- Dark mode support
Option B: Build Custom Editor (Full Control)
Use templates from this skill:
templates/base-editor.tsx- Minimal editor setuptemplates/common-extensions.ts- Extension bundletemplates/tiptap-prose.css- Tailwind styling
Key Points:
- Option A: Faster setup, opinionated UI
- Option B: Complete customization, headless approach
- Both work with React + Tailwind v4
Step 2: Configure Extensions
Extensions add functionality to your editor:
typescript1import StarterKit from '@tiptap/starter-kit' 2import Image from '@tiptap/extension-image' 3import Link from '@tiptap/extension-link' 4import Typography from '@tiptap/extension-typography' 5 6const editor = useEditor({ 7 extensions: [ 8 StarterKit.configure({ 9 // Customize built-in extensions 10 heading: { 11 levels: [1, 2, 3], 12 }, 13 bulletList: { 14 keepMarks: true, 15 }, 16 }), 17 Image.configure({ 18 inline: true, 19 allowBase64: false, // ⚠️ Prevent base64 bloat 20 resize: { 21 enabled: true, 22 directions: ['top-right', 'bottom-right', 'bottom-left', 'top-left'], 23 minWidth: 100, 24 minHeight: 100, 25 alwaysPreserveAspectRatio: true, 26 }, 27 }), 28 Link.configure({ 29 openOnClick: false, 30 HTMLAttributes: { 31 class: 'text-primary underline', 32 }, 33 }), 34 Typography, // Smart quotes, dashes, etc. 35 ], 36})
CRITICAL:
- Set
allowBase64: falseto prevent huge JSON payloads - Use upload handler pattern (see templates/image-upload-r2.tsx)
- Extension order matters - dependencies must load first
Step 3: Handle Image Uploads (If Needed)
Pattern: Base64 preview → background upload → replace with URL
See templates/image-upload-r2.tsx for full implementation:
typescript1import { Editor } from '@tiptap/core' 2 3async function uploadImageToR2(file: File, env: Env): Promise<string> { 4 // 1. Create base64 preview for immediate display 5 const reader = new FileReader() 6 const base64 = await new Promise<string>((resolve) => { 7 reader.onload = () => resolve(reader.result as string) 8 reader.readAsDataURL(file) 9 }) 10 11 // 2. Insert preview into editor 12 editor.chain().focus().setImage({ src: base64 }).run() 13 14 // 3. Upload to R2 in background 15 const formData = new FormData() 16 formData.append('file', file) 17 18 const response = await fetch('/api/upload', { 19 method: 'POST', 20 body: formData, 21 }) 22 23 const { url } = await response.json() 24 25 // 4. Replace base64 with permanent URL 26 editor.chain() 27 .focus() 28 .updateAttributes('image', { src: url }) 29 .run() 30 31 return url 32}
Why this pattern:
- Immediate user feedback (preview)
- No database bloat from base64
- Works with Cloudflare R2
- Graceful error handling
Critical Rules
Always Do
✅ Set immediatelyRender: false in useEditor() for SSR apps
✅ Install @tailwindcss/typography for prose styling
✅ Use upload handler for images (not base64)
✅ Memoize editor configuration to prevent re-renders
✅ Include @tiptap/pm peer dependency
Never Do
❌ Use immediatelyRender: true (default) with Next.js/SSR
❌ Store images as base64 in database (use URL after upload)
❌ Forget to add prose classes to editor container
❌ Load more than 100 widgets in collaborative mode
❌ Use Create React App (v3 incompatible - use Vite)
Known Issues Prevention
This skill prevents 7 documented issues:
Issue #1: SSR Hydration Mismatch
Error: "SSR has been detected, please set immediatelyRender explicitly to false"
Source: GitHub Issue #5856, #5602
Why It Happens: Default immediatelyRender: true breaks Next.js hydration
Prevention: Template includes immediatelyRender: false by default
Issue #2: Editor Re-renders on Every Keystroke
Error: Laggy typing, poor performance in large documents
Source: Tiptap Performance Docs
Why It Happens: useEditor() hook re-renders component on every change
Prevention: Use useEditorState() hook or memoization patterns (see templates)
Issue #3: Tailwind Typography Not Working
Error: Headings/lists render unstyled, no formatting visible
Source: shadcn Tiptap Discussion
Why It Happens: Missing @tailwindcss/typography plugin
Prevention: Skill includes typography plugin installation in checklist
Issue #4: Image Upload Base64 Bloat
Error: JSON payloads become megabytes, slow saves, database bloat Source: Tiptap Image Docs Why It Happens: Default allows base64, no upload handler configured Prevention: R2 upload template with URL replacement pattern
Issue #5: Build Errors in Create React App
Error: "jsx-runtime" module resolution errors after upgrading to v3 Source: GitHub Issue #6812 Why It Happens: CRA incompatibility with v3 module structure Prevention: Skill documents Vite as preferred bundler + provides working config
Issue #6: ProseMirror Multiple Versions Conflict
Error: Error: Looks like multiple versions of prosemirror-model were loaded
Source: GitHub Issue #577 (131 comments), Issue #6171
Why It Happens: Installing additional Tiptap extensions can pull different versions of prosemirror-model or prosemirror-view, creating duplicate dependencies in node_modules. The unique-id extension is particularly problematic in testing environments.
Prevention: Use package resolutions to force a single ProseMirror version
json1// package.json 2{ 3 "resolutions": { 4 "prosemirror-model": "~1.21.0", 5 "prosemirror-view": "~1.33.0", 6 "prosemirror-state": "~1.4.3" 7 } 8}
Or reinstall dependencies:
bash1rm -rf node_modules package-lock.json 2npm install
Note: The @tiptap/pm package is designed to prevent this issue, but extensions may still introduce conflicts.
Issue #7: EditorProvider vs useEditor Confusion (Community-sourced)
Error: SSR has been detected, please set 'immediatelyRender' explicitly to 'false' (when both used together)
Source: GitHub Issue #5856 Comment
Why It Happens: Users commonly use EditorProvider and useEditor together, but EditorProvider is a wrapper around useEditor for React Context setup - they should not be used simultaneously.
Prevention: Choose one pattern only
Incorrect Pattern:
typescript1// Don't use both together 2<EditorProvider> 3 <MyComponent /> 4</EditorProvider> 5 6function MyComponent() { 7 const editor = useEditor({ ... }) // ❌ Wrong - EditorProvider already created editor 8}
Correct Patterns:
typescript1// Option 1: Use EditorProvider only 2<EditorProvider immediatelyRender={false} extensions={[StarterKit]}> 3 <EditorContent /> 4</EditorProvider> 5 6// Option 2: Use useEditor only 7function Editor() { 8 const editor = useEditor({ 9 extensions: [StarterKit], 10 immediatelyRender: false, 11 }) 12 return <EditorContent editor={editor} /> 13}
Configuration Files Reference
Tailwind Prose Styling (tiptap-prose.css)
css1/* Apply to editor container */ 2.tiptap { 3 /* Tailwind Typography */ 4 @apply prose prose-sm sm:prose-base lg:prose-lg dark:prose-invert max-w-none; 5 6 /* Custom overrides */ 7 h1 { 8 @apply text-3xl font-bold mt-8 mb-4; 9 } 10 11 h2 { 12 @apply text-2xl font-semibold mt-6 mb-3; 13 } 14 15 p { 16 @apply my-4 text-base leading-7; 17 } 18 19 ul, ol { 20 @apply my-4 ml-6; 21 } 22 23 code { 24 @apply bg-muted px-1.5 py-0.5 rounded text-sm font-mono; 25 } 26 27 pre { 28 @apply bg-muted p-4 rounded-lg overflow-x-auto; 29 } 30 31 blockquote { 32 @apply border-l-4 border-primary pl-4 italic my-4; 33 } 34}
Why these settings:
proseclasses provide consistent formattingdark:prose-inverthandles dark mode automatically- Custom overrides use semantic Tailwind v4 colors
Common Patterns
Pattern 1: Collaborative Editing with Y.js
typescript1import { useEditor } from '@tiptap/react' 2import Collaboration from '@tiptap/extension-collaboration' 3import * as Y from 'yjs' 4 5const ydoc = new Y.Doc() 6 7const editor = useEditor({ 8 extensions: [ 9 StarterKit.configure({ 10 history: false, // Disable history for collaboration 11 }), 12 Collaboration.configure({ 13 document: ydoc, 14 }), 15 ], 16})
When to use: Real-time multi-user editing (Notion-like)
See: templates/collaborative-setup.tsx for full example
Pattern 2: Markdown Support
typescript1import { useEditor } from '@tiptap/react' 2import StarterKit from '@tiptap/starter-kit' 3import { Markdown } from '@tiptap/markdown' 4 5// Load editor with markdown content 6const editor = useEditor({ 7 extensions: [StarterKit, Markdown], 8 content: '# Hello World\n\nThis is **Markdown**!', 9 contentType: 'markdown', // ⚠️ CRITICAL: Must specify or content parsed as HTML 10 immediatelyRender: false, 11}) 12 13// Get markdown from editor 14const markdownOutput = editor.getMarkdown() 15 16// Insert markdown content 17editor.commands.setContent('## New heading', { contentType: 'markdown' }) 18editor.commands.insertContent('**Bold** text', { contentType: 'markdown' })
When to use: Storing content as markdown, displaying/editing rich text
Install: npm install @tiptap/markdown@3.16.0
Status: Beta (released Oct 2025, API stable but may change)
CRITICAL: Always specify contentType: 'markdown' when setting markdown content
Recent Fixes (v3.15.0-v3.16.0):
- Fixed incorrect Markdown output when underline is mixed with bold/italic and ranges don't fully overlap
- Improved serialization for overlapping formatting marks
- Source: v3.16.0 Release
Pattern 3: Form Integration with react-hook-form
typescript1import { useForm, Controller } from 'react-hook-form' 2 3function BlogForm() { 4 const { control, handleSubmit } = useForm() 5 6 return ( 7 <form onSubmit={handleSubmit(onSubmit)}> 8 <Controller 9 name="content" 10 control={control} 11 render={({ field }) => ( 12 <Editor 13 content={field.value} 14 onUpdate={({ editor }) => { 15 field.onChange(editor.getHTML()) 16 }} 17 /> 18 )} 19 /> 20 </form> 21 ) 22}
When to use: Blog posts, comments, any form-based content
Using Bundled Resources
Scripts (scripts/)
No executable scripts for this skill.
Templates (templates/)
Required for all projects:
templates/base-editor.tsx- Minimal React editor componenttemplates/package.json- Required dependencies
Optional based on needs:
templates/minimal-tiptap-setup.sh- shadcn component installationtemplates/image-upload-r2.tsx- R2 upload handlertemplates/tiptap-prose.css- Tailwind stylingtemplates/collaborative-setup.tsx- Y.js collaborationtemplates/common-extensions.ts- Extension bundle
When to load these: Claude should reference templates when user asks to:
- Set up tiptap editor
- Add image uploads
- Configure collaborative editing
- Style with Tailwind prose
References (references/)
references/tiptap-docs.md- Key documentation linksreferences/common-errors.md- Error troubleshooting guidereferences/extension-catalog.md- Popular extensions list
When Claude should load these: Troubleshooting errors, exploring extensions, understanding API
Advanced Topics
Custom Extensions
Create your own Tiptap extensions:
typescript1import { Node } from '@tiptap/core' 2 3const CustomNode = Node.create({ 4 name: 'customNode', 5 6 group: 'block', 7 8 content: 'inline*', 9 10 parseHTML() { 11 return [{ tag: 'div[data-custom]' }] 12 }, 13 14 renderHTML({ HTMLAttributes }) { 15 return ['div', { 'data-custom': '', ...HTMLAttributes }, 0] 16 }, 17 18 addCommands() { 19 return { 20 insertCustomNode: () => ({ commands }) => { 21 return commands.insertContent({ type: this.name }) 22 }, 23 } 24 }, 25})
Use cases: Custom widgets, embeds, interactive elements
Slash Commands
Add Notion-like / commands:
typescript1import { Extension } from '@tiptap/core' 2import Suggestion from '@tiptap/suggestion' 3 4const SlashCommands = Extension.create({ 5 name: 'slashCommands', 6 7 addOptions() { 8 return { 9 suggestion: { 10 char: '/', 11 items: ({ query }) => { 12 return [ 13 { title: 'Heading 1', command: ({ editor, range }) => { 14 editor.chain().focus().deleteRange(range).setHeading({ level: 1 }).run() 15 }}, 16 { title: 'Bullet List', command: ({ editor, range }) => { 17 editor.chain().focus().deleteRange(range).toggleBulletList().run() 18 }}, 19 ] 20 }, 21 }, 22 } 23 }, 24 25 addProseMirrorPlugins() { 26 return [Suggestion({ editor: this.editor, ...this.options.suggestion })] 27 }, 28})
Use cases: Productivity shortcuts, quick formatting
Dependencies
Required:
@tiptap/react@^3.16.0- React integration (React 19 supported)@tiptap/starter-kit@^3.16.0- Essential extensions bundle@tiptap/pm@^3.16.0- ProseMirror peer dependencyreact@^19.0.0- React framework
React Version Compatibility:
- Core Tiptap: Supports React 19 as of v2.10.0
- UI Components: Work best with React 18 (Next.js 15 recommended per official docs)
- Pro Extensions: May require React 18 - drag-handle extension depends on archived tippyjs-react without React 19 support (Issue #5876)
Optional:
@tiptap/extension-audio@^3.16.0- Audio support (NEW in v3.16.0)@tiptap/extension-image@^3.16.0- Image support@tiptap/extension-link@^3.16.0- Link support (NEW in v3, included in StarterKit)@tiptap/extension-color@^3.16.0- Text color@tiptap/extension-typography@^3.16.0- Smart typography@tiptap/extension-collaboration@^3.16.0- Real-time collaboration@tiptap/extension-markdown@^3.16.0- Markdown support (Beta)@tailwindcss/typography@^0.5.19- Prose stylingyjs@^13.6.0- Collaborative editing backendreact-medium-image-zoom@^5.2.0- Image zoom functionality
Official Documentation
- Tiptap: https://tiptap.dev
- Installation Guide: https://tiptap.dev/docs/editor/installation/react
- Extensions: https://tiptap.dev/docs/editor/extensions
- API Reference: https://tiptap.dev/docs/editor/api/editor
- shadcn minimal-tiptap: https://github.com/Aslam97/shadcn-minimal-tiptap
- Context7 Library ID: tiptap/tiptap
Package Versions (Verified 2026-01-21)
json1{ 2 "dependencies": { 3 "@tiptap/react": "^3.16.0", 4 "@tiptap/starter-kit": "^3.16.0", 5 "@tiptap/pm": "^3.16.0", 6 "@tiptap/extension-audio": "^3.16.0", 7 "@tiptap/extension-image": "^3.16.0", 8 "@tiptap/extension-color": "^3.16.0", 9 "@tiptap/extension-text-style": "^3.16.0", 10 "@tiptap/extension-typography": "^3.16.0", 11 "@tiptap/extension-link": "^3.16.0", 12 "@tiptap/extension-markdown": "^3.16.0" 13 }, 14 "devDependencies": { 15 "@tailwindcss/typography": "^0.5.19", 16 "react": "^19.2.3", 17 "react-dom": "^19.2.3" 18 } 19}
Production Example
This skill is based on real-world implementations:
- GitLab: Uses Tiptap for issue/MR descriptions
- Statamic CMS: Tiptap as default rich text editor
- shadcn minimal-tiptap: 3.14M downloads/week
Token Savings: ~71% (14k → 4k tokens) Errors Prevented: 7/7 documented errors (5 critical setup + 2 community patterns) Validation: ✅ SSR compatibility, ✅ Image uploads, ✅ Tailwind v4, ✅ Performance, ✅ React 19 compatibility
Troubleshooting
Problem: "SSR has been detected, please set immediatelyRender explicitly to false"
Solution: Add immediatelyRender: false to your useEditor() config
Problem: Headings/lists look unstyled
Solution: Install @tailwindcss/typography and add prose classes to editor container
Problem: Editor lags when typing
Solution: Use useEditorState() hook instead of useEditor() for read-only rendering, or memoize editor configuration
Problem: Images make JSON huge
Solution: Set allowBase64: false in Image extension config and use upload handler (see templates/image-upload-r2.tsx)
Problem: Build fails in Create React App
Solution: Switch to Vite - CRA incompatible with Tiptap v3. See cloudflare-worker-base skill for Vite setup.
Complete Setup Checklist
Use this checklist to verify your setup:
- Installed
@tiptap/react,@tiptap/starter-kit,@tiptap/pm - Set
immediatelyRender: falseinuseEditor()config - Installed
@tailwindcss/typographyplugin - Added
proseclasses to editor container - Configured image upload handler (if using images)
- Set
allowBase64: falsein Image extension - Editor renders without hydration errors
- Formatted text displays correctly (headings, lists, etc.)
- Dev server runs without TypeScript errors
- Production build succeeds
Questions? Issues?
- Check
references/common-errors.mdfor troubleshooting - Verify
immediatelyRender: falseis set - Check official docs: https://tiptap.dev
- Ensure
@tiptap/pmpeer dependency is installed