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)
- Install Dependencies
npm 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/pm is 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.
- Create SSR-Safe Editor
import { useEditor, EditorContent } from '@tiptap/react' import StarterKit from '@tiptap/starter-kit'
export function Editor() { const editor = useEditor({ extensions: [StarterKit], content: '<p>Hello World!</p>', immediatelyRender: false, // ⚠️ CRITICAL for SSR/Next.js editorProps: { attributes: { class: 'prose prose-sm focus:outline-none min-h-[200px] p-4', }, }, })
return <EditorContent editor={editor} /> }
CRITICAL:
-
Always set immediatelyRender: false for Next.js/SSR apps (prevents hydration mismatch)
-
Without this, you'll see: "SSR has been detected, please set immediatelyRender explicitly to false "
-
This is the #1 error reported by Tiptap users
- Add Tailwind Typography (Optional but Recommended)
npm install @tailwindcss/typography
Update your tailwind.config.ts :
import typography from '@tailwindcss/typography'
export default { plugins: [typography], }
Why this matters:
-
Provides default prose styling for headings, lists, links, etc.
-
Without it, formatted content looks unstyled
-
Alternative: Use custom Tailwind classes with .tiptap selector
The 3-Step Setup Process
Step 1: Choose Your Integration Method
Option A: shadcn Minimal Tiptap Component (Recommended)
Install the pre-built shadcn component:
npx 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 setup
-
templates/common-extensions.ts
-
Extension bundle
-
templates/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:
import StarterKit from '@tiptap/starter-kit' import Image from '@tiptap/extension-image' import Link from '@tiptap/extension-link' import Typography from '@tiptap/extension-typography'
const editor = useEditor({ extensions: [ StarterKit.configure({ // Customize built-in extensions heading: { levels: [1, 2, 3], }, bulletList: { keepMarks: true, }, }), Image.configure({ inline: true, allowBase64: false, // ⚠️ Prevent base64 bloat resize: { enabled: true, directions: ['top-right', 'bottom-right', 'bottom-left', 'top-left'], minWidth: 100, minHeight: 100, alwaysPreserveAspectRatio: true, }, }), Link.configure({ openOnClick: false, HTMLAttributes: { class: 'text-primary underline', }, }), Typography, // Smart quotes, dashes, etc. ], })
CRITICAL:
-
Set allowBase64: false to 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:
import { Editor } from '@tiptap/core'
async function uploadImageToR2(file: File, env: Env): Promise<string> { // 1. Create base64 preview for immediate display const reader = new FileReader() const base64 = await new Promise<string>((resolve) => { reader.onload = () => resolve(reader.result as string) reader.readAsDataURL(file) })
// 2. Insert preview into editor editor.chain().focus().setImage({ src: base64 }).run()
// 3. Upload to R2 in background const formData = new FormData() formData.append('file', file)
const response = await fetch('/api/upload', { method: 'POST', body: formData, })
const { url } = await response.json()
// 4. Replace base64 with permanent URL editor.chain() .focus() .updateAttributes('image', { src: url }) .run()
return url }
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
// package.json { "resolutions": { "prosemirror-model": "~1.21.0", "prosemirror-view": "~1.33.0", "prosemirror-state": "~1.4.3" } }
Or reinstall dependencies:
rm -rf node_modules package-lock.json npm 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:
// Don't use both together <EditorProvider> <MyComponent /> </EditorProvider>
function MyComponent() { const editor = useEditor({ ... }) // ❌ Wrong - EditorProvider already created editor }
Correct Patterns:
// Option 1: Use EditorProvider only <EditorProvider immediatelyRender={false} extensions={[StarterKit]}> <EditorContent /> </EditorProvider>
// Option 2: Use useEditor only function Editor() { const editor = useEditor({ extensions: [StarterKit], immediatelyRender: false, }) return <EditorContent editor={editor} /> }
Configuration Files Reference
Tailwind Prose Styling (tiptap-prose.css)
/* Apply to editor container / .tiptap { / Tailwind Typography */ @apply prose prose-sm sm:prose-base lg:prose-lg dark:prose-invert max-w-none;
/* Custom overrides */ h1 { @apply text-3xl font-bold mt-8 mb-4; }
h2 { @apply text-2xl font-semibold mt-6 mb-3; }
p { @apply my-4 text-base leading-7; }
ul, ol { @apply my-4 ml-6; }
code { @apply bg-muted px-1.5 py-0.5 rounded text-sm font-mono; }
pre { @apply bg-muted p-4 rounded-lg overflow-x-auto; }
blockquote { @apply border-l-4 border-primary pl-4 italic my-4; } }
Why these settings:
-
prose classes provide consistent formatting
-
dark:prose-invert handles dark mode automatically
-
Custom overrides use semantic Tailwind v4 colors
Common Patterns
Pattern 1: Collaborative Editing with Y.js
import { useEditor } from '@tiptap/react' import Collaboration from '@tiptap/extension-collaboration' import * as Y from 'yjs'
const ydoc = new Y.Doc()
const editor = useEditor({ extensions: [ StarterKit.configure({ history: false, // Disable history for collaboration }), Collaboration.configure({ document: ydoc, }), ], })
When to use: Real-time multi-user editing (Notion-like) See: templates/collaborative-setup.tsx for full example
Pattern 2: Markdown Support
import { useEditor } from '@tiptap/react' import StarterKit from '@tiptap/starter-kit' import { Markdown } from '@tiptap/markdown'
// Load editor with markdown content const editor = useEditor({ extensions: [StarterKit, Markdown], content: '# Hello World\n\nThis is Markdown!', contentType: 'markdown', // ⚠️ CRITICAL: Must specify or content parsed as HTML immediatelyRender: false, })
// Get markdown from editor const markdownOutput = editor.getMarkdown()
// Insert markdown content editor.commands.setContent('## New heading', { contentType: 'markdown' }) editor.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
import { useForm, Controller } from 'react-hook-form'
function BlogForm() { const { control, handleSubmit } = useForm()
return ( <form onSubmit={handleSubmit(onSubmit)}> <Controller name="content" control={control} render={({ field }) => ( <Editor content={field.value} onUpdate={({ editor }) => { field.onChange(editor.getHTML()) }} /> )} /> </form> ) }
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 component
-
templates/package.json
-
Required dependencies
Optional based on needs:
-
templates/minimal-tiptap-setup.sh
-
shadcn component installation
-
templates/image-upload-r2.tsx
-
R2 upload handler
-
templates/tiptap-prose.css
-
Tailwind styling
-
templates/collaborative-setup.tsx
-
Y.js collaboration
-
templates/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 links
-
references/common-errors.md
-
Error troubleshooting guide
-
references/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:
import { Node } from '@tiptap/core'
const CustomNode = Node.create({ name: 'customNode',
group: 'block',
content: 'inline*',
parseHTML() { return [{ tag: 'div[data-custom]' }] },
renderHTML({ HTMLAttributes }) { return ['div', { 'data-custom': '', ...HTMLAttributes }, 0] },
addCommands() { return { insertCustomNode: () => ({ commands }) => { return commands.insertContent({ type: this.name }) }, } }, })
Use cases: Custom widgets, embeds, interactive elements
Slash Commands
Add Notion-like / commands:
import { Extension } from '@tiptap/core' import Suggestion from '@tiptap/suggestion'
const SlashCommands = Extension.create({ name: 'slashCommands',
addOptions() { return { suggestion: { char: '/', items: ({ query }) => { return [ { title: 'Heading 1', command: ({ editor, range }) => { editor.chain().focus().deleteRange(range).setHeading({ level: 1 }).run() }}, { title: 'Bullet List', command: ({ editor, range }) => { editor.chain().focus().deleteRange(range).toggleBulletList().run() }}, ] }, }, } },
addProseMirrorPlugins() { return [Suggestion({ editor: this.editor, ...this.options.suggestion })] }, })
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 dependency
-
react@^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 styling
-
yjs@^13.6.0
-
Collaborative editing backend
-
react-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)
{ "dependencies": { "@tiptap/react": "^3.16.0", "@tiptap/starter-kit": "^3.16.0", "@tiptap/pm": "^3.16.0", "@tiptap/extension-audio": "^3.16.0", "@tiptap/extension-image": "^3.16.0", "@tiptap/extension-color": "^3.16.0", "@tiptap/extension-text-style": "^3.16.0", "@tiptap/extension-typography": "^3.16.0", "@tiptap/extension-link": "^3.16.0", "@tiptap/extension-markdown": "^3.16.0" }, "devDependencies": { "@tailwindcss/typography": "^0.5.19", "react": "^19.2.3", "react-dom": "^19.2.3" } }
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: false in useEditor() config
-
Installed @tailwindcss/typography plugin
-
Added prose classes to editor container
-
Configured image upload handler (if using images)
-
Set allowBase64: false in 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.md for troubleshooting
-
Verify immediatelyRender: false is set
-
Check official docs: https://tiptap.dev
-
Ensure @tiptap/pm peer dependency is installed