tiptap
Build rich text editors with Tiptap - headless editor framework with React and Tailwind v4. Covers SSR-safe setup, image uploads, prose styling, and collaborative editing. Use when creating blog editors, comment systems, or Notion-like apps, or troubleshooting SSR hydration errors, typography issues, or image upload problems.
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install jezweb-claude-skills-tiptap
Repository
Skill path: skills/tiptap
Build rich text editors with Tiptap - headless editor framework with React and Tailwind v4. Covers SSR-safe setup, image uploads, prose styling, and collaborative editing. Use when creating blog editors, comment systems, or Notion-like apps, or troubleshooting SSR hydration errors, typography issues, or image upload problems.
Open repositoryBest for
Primary workflow: Ship Full Stack.
Technical facets: Full Stack, Frontend.
Target audience: React developers building rich text editors for blogs, comment systems, or collaborative apps using Tiptap with Tailwind CSS.
License: Unknown.
Original source
Catalog source: SkillHub Club.
Repository owner: jezweb.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install tiptap into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/jezweb/claude-skills before adding tiptap to shared team environments
- Use tiptap for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
--- name: tiptap description: | Build rich text editors with Tiptap - headless editor framework with React and Tailwind v4. Covers SSR-safe setup, image uploads, prose styling, and collaborative editing. Use when creating blog editors, comment systems, or Notion-like apps, or troubleshooting SSR hydration errors, typography issues, or image upload problems. user-invocable: true --- # Tiptap Rich Text Editor **Status**: Production Ready **Last Updated**: 2026-01-21 **Dependencies**: React 19+, Tailwind v4, shadcn/ui (recommended) **Latest Versions**: @tiptap/[email protected], @tiptap/[email protected], @tiptap/[email protected] (verified 2026-01-21) --- ## Quick Start (5 Minutes) ### 1. Install Dependencies ```bash 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. ### 2. Create SSR-Safe Editor ```typescript 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 ### 3. Add Tailwind Typography (Optional but Recommended) ```bash npm install @tailwindcss/typography ``` Update your `tailwind.config.ts`: ```typescript 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: ```bash 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: ```typescript 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: ```typescript 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](https://github.com/ueberdosis/tiptap/issues/5856), [#5602](https://github.com/ueberdosis/tiptap/issues/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](https://tiptap.dev/docs/editor/api/editor#immediatelyrender) **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](https://github.com/shadcn-ui/ui/discussions/1729) **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](https://tiptap.dev/docs/editor/extensions/nodes/image#usage) **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](https://github.com/ueberdosis/tiptap/issues/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](https://github.com/ueberdosis/tiptap/issues/577) (131 comments), [Issue #6171](https://github.com/ueberdosis/tiptap/issues/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 ```json // package.json { "resolutions": { "prosemirror-model": "~1.21.0", "prosemirror-view": "~1.33.0", "prosemirror-state": "~1.4.3" } } ``` Or reinstall dependencies: ```bash 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](https://github.com/ueberdosis/tiptap/issues/5856#issuecomment-2493124171) **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**: ```typescript // Don't use both together <EditorProvider> <MyComponent /> </EditorProvider> function MyComponent() { const editor = useEditor({ ... }) // ❌ Wrong - EditorProvider already created editor } ``` **Correct Patterns**: ```typescript // 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) ```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 ```typescript 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 ```typescript 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/[email protected]` **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](https://github.com/ueberdosis/tiptap/releases/tag/v3.16.0) ### Pattern 3: Form Integration with react-hook-form ```typescript 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: ```typescript 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: ```typescript 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](https://tiptap.dev/docs/editor/getting-started/install/nextjs)) - **Pro Extensions**: May require React 18 - drag-handle extension depends on archived tippyjs-react without React 19 support ([Issue #5876](https://github.com/ueberdosis/tiptap/issues/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) ```json { "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?** 1. Check `references/common-errors.md` for troubleshooting 2. Verify `immediatelyRender: false` is set 3. Check official docs: https://tiptap.dev 4. Ensure `@tiptap/pm` peer dependency is installed --- ## Referenced Files > The following files are referenced in this skill and included for context. ### templates/base-editor.tsx ```tsx import { useEditor, EditorContent } from '@tiptap/react' import StarterKit from '@tiptap/starter-kit' import { useEffect } from 'react' interface EditorProps { content?: string onUpdate?: (content: string) => void placeholder?: string editable?: boolean className?: string } /** * Base Tiptap Editor Component * * Features: * - SSR-safe (immediatelyRender: false) * - Tailwind Typography prose classes * - StarterKit with all basic extensions * - Update callback for form integration * - Customizable placeholder * - Editable/readonly modes * * Usage: * ```tsx * import { Editor } from '@/components/editor' * * function MyComponent() { * const [content, setContent] = useState('') * * return ( * <Editor * content={content} * onUpdate={setContent} * placeholder="Start writing..." * /> * ) * } * ``` */ export function Editor({ content = '', onUpdate, placeholder = 'Start writing...', editable = true, className = '', }: EditorProps) { const editor = useEditor({ extensions: [ StarterKit.configure({ heading: { levels: [1, 2, 3, 4], }, bulletList: { keepMarks: true, keepAttributes: false, }, orderedList: { keepMarks: true, keepAttributes: false, }, }), ], content, editable, immediatelyRender: false, // ⚠️ CRITICAL: Prevents SSR hydration errors editorProps: { attributes: { class: `prose prose-sm sm:prose-base lg:prose-lg dark:prose-invert max-w-none focus:outline-none min-h-[200px] p-4 ${className}`, 'data-placeholder': placeholder, }, }, onUpdate: ({ editor }) => { onUpdate?.(editor.getHTML()) }, }) // Sync content changes from parent useEffect(() => { if (editor && content !== editor.getHTML()) { editor.commands.setContent(content) } }, [content, editor]) if (!editor) { return null } return ( <div className="border border-border rounded-lg bg-background"> <EditorContent editor={editor} /> </div> ) } /** * Editor with Toolbar * * Includes basic formatting toolbar above editor content. * For more advanced toolbars, use shadcn minimal-tiptap component. */ export function EditorWithToolbar(props: EditorProps) { const editor = useEditor({ extensions: [StarterKit], content: props.content, immediatelyRender: false, onUpdate: ({ editor }) => { props.onUpdate?.(editor.getHTML()) }, }) if (!editor) { return null } return ( <div className="border border-border rounded-lg bg-background"> {/* Basic Toolbar */} <div className="flex flex-wrap gap-1 p-2 border-b border-border"> <button onClick={() => editor.chain().focus().toggleBold().run()} className={`px-3 py-1 rounded hover:bg-accent ${ editor.isActive('bold') ? 'bg-accent' : '' }`} type="button" > Bold </button> <button onClick={() => editor.chain().focus().toggleItalic().run()} className={`px-3 py-1 rounded hover:bg-accent ${ editor.isActive('italic') ? 'bg-accent' : '' }`} type="button" > Italic </button> <button onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()} className={`px-3 py-1 rounded hover:bg-accent ${ editor.isActive('heading', { level: 2 }) ? 'bg-accent' : '' }`} type="button" > H2 </button> <button onClick={() => editor.chain().focus().toggleBulletList().run()} className={`px-3 py-1 rounded hover:bg-accent ${ editor.isActive('bulletList') ? 'bg-accent' : '' }`} type="button" > List </button> </div> {/* Editor Content */} <EditorContent editor={editor} className="p-4" /> </div> ) } ``` ### templates/common-extensions.ts ```typescript /** * Common Tiptap Extensions Bundle * * Pre-configured extensions for typical use cases: * - Blog editors * - Comment systems * - Documentation platforms * - Rich text inputs * * Installation: * npm install @tiptap/react @tiptap/starter-kit @tiptap/pm @tiptap/extension-image @tiptap/extension-link @tiptap/extension-typography @tiptap/extension-placeholder @tiptap/extension-text-style @tiptap/extension-color */ import StarterKit from '@tiptap/starter-kit' import Image from '@tiptap/extension-image' import Link from '@tiptap/extension-link' import Typography from '@tiptap/extension-typography' import Placeholder from '@tiptap/extension-placeholder' import TextStyle from '@tiptap/extension-text-style' import Color from '@tiptap/extension-color' import type { Extensions } from '@tiptap/react' /** * Minimal Extension Set * * For simple text inputs (comments, descriptions, etc.) * Includes: Bold, Italic, Strike, Code, Hard Breaks */ export const minimalExtensions: Extensions = [ StarterKit.configure({ // Disable features not needed for simple inputs heading: false, bulletList: false, orderedList: false, blockquote: false, codeBlock: false, horizontalRule: false, }), ] /** * Basic Extension Set * * For comment systems and basic rich text * Adds: Lists, Links, Typography */ export const basicExtensions: Extensions = [ StarterKit.configure({ heading: { levels: [2, 3], // Only H2, H3 for comments }, }), Link.configure({ openOnClick: false, HTMLAttributes: { class: 'text-primary underline underline-offset-2 hover:text-primary/80', rel: 'noopener noreferrer', target: '_blank', }, }), Typography, Placeholder.configure({ placeholder: 'Write a comment...', }), ] /** * Standard Extension Set * * For blog posts, articles, documentation * Adds: Images, Color, All headings */ export const standardExtensions: Extensions = [ StarterKit.configure({ heading: { levels: [1, 2, 3, 4], }, bulletList: { keepMarks: true, keepAttributes: false, }, orderedList: { keepMarks: true, keepAttributes: false, }, }), Image.configure({ inline: true, allowBase64: false, // Use upload handler instead HTMLAttributes: { class: 'rounded-lg max-w-full h-auto', }, }), Link.configure({ openOnClick: false, HTMLAttributes: { class: 'text-primary underline underline-offset-2 hover:text-primary/80', rel: 'noopener noreferrer', target: '_blank', }, }), Typography, TextStyle, Color, Placeholder.configure({ placeholder: 'Start writing...', }), ] /** * Advanced Extension Set * * For full-featured editors (Notion-like) * Adds: Task lists, tables, code block lowlight, etc. * * Additional installs required: * npm install @tiptap/extension-task-list @tiptap/extension-task-item @tiptap/extension-table @tiptap/extension-table-row @tiptap/extension-table-cell @tiptap/extension-table-header @tiptap/extension-code-block-lowlight lowlight */ import TaskList from '@tiptap/extension-task-list' import TaskItem from '@tiptap/extension-task-item' import Table from '@tiptap/extension-table' import TableRow from '@tiptap/extension-table-row' import TableCell from '@tiptap/extension-table-cell' import TableHeader from '@tiptap/extension-table-header' import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight' import { common, createLowlight } from 'lowlight' const lowlight = createLowlight(common) export const advancedExtensions: Extensions = [ StarterKit.configure({ heading: { levels: [1, 2, 3, 4, 5, 6], }, codeBlock: false, // Replace with lowlight version }), CodeBlockLowlight.configure({ lowlight, HTMLAttributes: { class: 'rounded-lg bg-muted p-4 font-mono text-sm overflow-x-auto', }, }), Image.configure({ inline: true, allowBase64: false, HTMLAttributes: { class: 'rounded-lg max-w-full h-auto', }, }), Link.configure({ openOnClick: false, HTMLAttributes: { class: 'text-primary underline underline-offset-2 hover:text-primary/80', rel: 'noopener noreferrer', target: '_blank', }, }), TaskList.configure({ HTMLAttributes: { class: 'list-none ml-0', }, }), TaskItem.configure({ nested: true, HTMLAttributes: { class: 'flex items-start gap-2', }, }), Table.configure({ resizable: true, HTMLAttributes: { class: 'border-collapse w-full my-4', }, }), TableRow, TableHeader.configure({ HTMLAttributes: { class: 'border border-border px-4 py-2 bg-muted font-semibold', }, }), TableCell.configure({ HTMLAttributes: { class: 'border border-border px-4 py-2', }, }), Typography, TextStyle, Color, Placeholder.configure({ placeholder: 'Start writing...', }), ] /** * Collaborative Extension Set * * For real-time collaboration (Notion-like) * * Additional installs required: * npm install @tiptap/extension-collaboration @tiptap/extension-collaboration-cursor yjs y-websocket */ import Collaboration from '@tiptap/extension-collaboration' import CollaborationCursor from '@tiptap/extension-collaboration-cursor' import * as Y from 'yjs' export function getCollaborativeExtensions(ydoc: Y.Doc, provider: any, user: { name: string; color: string }) { return [ StarterKit.configure({ history: false, // Disable local history for collaboration }), Collaboration.configure({ document: ydoc, }), CollaborationCursor.configure({ provider, user, }), Image.configure({ inline: true, allowBase64: false, }), Link.configure({ openOnClick: false, }), Typography, ] } /** * Usage Examples: * * // Minimal (comments) * const editor = useEditor({ * extensions: minimalExtensions, * immediatelyRender: false, * }) * * // Basic (rich comments) * const editor = useEditor({ * extensions: basicExtensions, * immediatelyRender: false, * }) * * // Standard (blog posts) * const editor = useEditor({ * extensions: standardExtensions, * immediatelyRender: false, * }) * * // Advanced (full editor) * const editor = useEditor({ * extensions: advancedExtensions, * immediatelyRender: false, * }) * * // Collaborative * const ydoc = new Y.Doc() * const provider = new WebsocketProvider('ws://localhost:1234', 'doc-name', ydoc) * const editor = useEditor({ * extensions: getCollaborativeExtensions(ydoc, provider, { * name: 'John Doe', * color: '#3b82f6', * }), * immediatelyRender: false, * }) */ /** * Custom Extension Configuration Helpers */ /** * Get Link extension with custom validation */ export function getLinkExtensionWithValidation() { return Link.extend({ addOptions() { return { ...this.parent?.(), validate: (href: string) => { // Only allow http(s) URLs return /^https?:\/\//.test(href) }, } }, }) } /** * Get Image extension with size limits */ export function getImageExtensionWithLimits(maxWidth = 800, maxHeight = 600) { return Image.configure({ inline: true, allowBase64: false, HTMLAttributes: { class: 'rounded-lg max-w-full h-auto', style: `max-width: ${maxWidth}px; max-height: ${maxHeight}px;`, }, }) } /** * Get Placeholder with dynamic text */ export function getPlaceholderExtension(text: string) { return Placeholder.configure({ placeholder: text, emptyEditorClass: 'is-editor-empty', emptyNodeClass: 'is-empty', showOnlyWhenEditable: true, showOnlyCurrent: true, }) } ``` ### templates/tiptap-prose.css ```css /** * Tiptap Prose Styling with Tailwind v4 * * Two approaches: * 1. Tailwind Typography plugin (@tailwindcss/typography) - Recommended * 2. Custom utility classes - Full control * * Installation: * npm install @tailwindcss/typography * * Add to tailwind.config.ts: * import typography from '@tailwindcss/typography' * export default { * plugins: [typography], * } */ /* ============================================ APPROACH 1: Tailwind Typography (Recommended) ============================================ */ /** * Apply prose classes directly to editor container: * * <EditorContent * editor={editor} * className="prose prose-sm sm:prose-base lg:prose-lg dark:prose-invert max-w-none" * /> * * Benefits: * - Automatic dark mode support (dark:prose-invert) * - Responsive sizing (prose-sm, prose-base, prose-lg) * - Consistent styling across all elements * - Community-maintained best practices */ /* ============================================ APPROACH 2: Custom Utility Classes ============================================ */ /** * Use .tiptap selector for full control over styling * * Apply to editor: * <div className="tiptap"> * <EditorContent editor={editor} /> * </div> */ .tiptap { /* Base prose styling */ @apply max-w-none; /* Typography */ font-family: ui-sans-serif, system-ui, sans-serif; font-size: 1rem; line-height: 1.75; color: var(--foreground); } /* Headings */ .tiptap h1 { @apply text-4xl font-bold mt-8 mb-4; color: var(--foreground); } .tiptap h2 { @apply text-3xl font-semibold mt-6 mb-3; color: var(--foreground); } .tiptap h3 { @apply text-2xl font-semibold mt-5 mb-2; color: var(--foreground); } .tiptap h4 { @apply text-xl font-semibold mt-4 mb-2; color: var(--foreground); } /* Paragraphs */ .tiptap p { @apply my-4 text-base leading-7; } .tiptap p:first-child { @apply mt-0; } .tiptap p:last-child { @apply mb-0; } /* Lists */ .tiptap ul, .tiptap ol { @apply my-4 ml-6; } .tiptap ul { @apply list-disc; } .tiptap ol { @apply list-decimal; } .tiptap li { @apply my-2; } .tiptap li p { @apply my-0; } /* Links */ .tiptap a { @apply text-primary underline underline-offset-2; @apply hover:text-primary/80 transition-colors; } /* Code */ .tiptap code { @apply bg-muted px-1.5 py-0.5 rounded text-sm font-mono; @apply before:content-[''] after:content-['']; color: var(--foreground); } .tiptap pre { @apply bg-muted p-4 rounded-lg overflow-x-auto my-4; @apply border border-border; } .tiptap pre code { @apply bg-transparent p-0; @apply text-sm leading-relaxed; } /* Blockquotes */ .tiptap blockquote { @apply border-l-4 border-primary pl-4 italic my-4; @apply text-muted-foreground; } /* Horizontal Rule */ .tiptap hr { @apply my-8 border-t border-border; } /* Images */ .tiptap img { @apply rounded-lg max-w-full h-auto my-4; @apply border border-border; } /* Tables (if using table extension) */ .tiptap table { @apply border-collapse w-full my-4; } .tiptap th, .tiptap td { @apply border border-border px-4 py-2 text-left; } .tiptap th { @apply bg-muted font-semibold; } /* Task Lists (if using task list extension) */ .tiptap ul[data-type="taskList"] { @apply list-none ml-0; } .tiptap li[data-type="taskItem"] { @apply flex items-start gap-2; } .tiptap input[type="checkbox"] { @apply mt-1; } /* ============================================ PLACEHOLDER STYLING ============================================ */ /** * Style empty editor placeholder * * Configure in useEditor(): * editorProps: { * attributes: { * 'data-placeholder': 'Start writing...' * } * } */ .tiptap p.is-editor-empty:first-child::before { content: attr(data-placeholder); @apply text-muted-foreground; float: left; height: 0; pointer-events: none; } /* ============================================ FOCUS STYLES ============================================ */ .tiptap:focus { @apply outline-none; } .tiptap.ProseMirror-focused { @apply outline-none; } /* ============================================ SELECTION STYLES ============================================ */ .tiptap ::selection { @apply bg-primary/20; } /* ============================================ RESPONSIVE ADJUSTMENTS ============================================ */ @media (max-width: 640px) { .tiptap h1 { @apply text-3xl; } .tiptap h2 { @apply text-2xl; } .tiptap h3 { @apply text-xl; } .tiptap pre { @apply text-xs; } } /* ============================================ DARK MODE OVERRIDES ============================================ */ /** * If not using dark:prose-invert, add dark mode styles: */ .dark .tiptap { color: var(--foreground); } .dark .tiptap h1, .dark .tiptap h2, .dark .tiptap h3, .dark .tiptap h4 { color: var(--foreground); } .dark .tiptap code { background-color: var(--muted); color: var(--foreground); } .dark .tiptap pre { background-color: var(--muted); border-color: var(--border); } .dark .tiptap blockquote { color: var(--muted-foreground); border-color: var(--primary); } /* ============================================ UTILITY: Remove default margins ============================================ */ /** * Use this class to remove default margins for compact layouts */ .tiptap-compact p, .tiptap-compact h1, .tiptap-compact h2, .tiptap-compact h3, .tiptap-compact h4, .tiptap-compact ul, .tiptap-compact ol, .tiptap-compact blockquote { @apply my-2; } .tiptap-compact h1 { @apply mt-4; } ``` ### templates/image-upload-r2.tsx ```tsx import { Editor } from '@tiptap/core' import Image from '@tiptap/extension-image' /** * Image Upload Handler for Cloudflare R2 * * Pattern: Base64 preview → background upload → replace with URL * * Benefits: * - Immediate user feedback (base64 preview) * - No database bloat (URL replaces base64) * - Works with Cloudflare R2 * - Graceful error handling * * Prerequisites: * - R2 bucket configured in wrangler.jsonc * - Upload API endpoint (see below) * - Image extension installed: npm install @tiptap/extension-image */ /** * Example Upload API Endpoint (Cloudflare Worker) * * Place this in your Worker's routes: * ```typescript * import { Env } from './types' * * export async function handleImageUpload(request: Request, env: Env) { * const formData = await request.formData() * const file = formData.get('file') as File * * if (!file) { * return Response.json({ error: 'No file provided' }, { status: 400 }) * } * * // Generate unique filename * const filename = `${crypto.randomUUID()}-${file.name}` * * // Upload to R2 * await env.IMAGES_BUCKET.put(filename, file.stream(), { * httpMetadata: { * contentType: file.type, * }, * }) * * // Return public URL (configure custom domain in R2 settings) * const url = `https://images.yourdomain.com/${filename}` * * return Response.json({ url }) * } * ``` */ interface UploadImageOptions { editor: Editor file: File uploadEndpoint?: string onProgress?: (progress: number) => void onError?: (error: Error) => void } /** * Upload image to R2 with base64 preview */ export async function uploadImageToR2({ editor, file, uploadEndpoint = '/api/upload', onProgress, onError, }: UploadImageOptions): Promise<string | null> { try { // 1. Create base64 preview for immediate display const base64 = await fileToBase64(file) // 2. Insert preview into editor (user sees image immediately) editor.chain().focus().setImage({ src: base64 }).run() onProgress?.(10) // Loading started // 3. Upload to R2 in background const formData = new FormData() formData.append('file', file) onProgress?.(50) // Upload in progress const response = await fetch(uploadEndpoint, { method: 'POST', body: formData, }) if (!response.ok) { throw new Error(`Upload failed: ${response.statusText}`) } const { url } = await response.json() onProgress?.(90) // Processing complete // 4. Replace base64 preview with permanent URL // Find the image node and update its src attribute const { state } = editor const { selection } = state const pos = selection.$from.pos // Update the image that was just inserted editor.chain() .focus() .updateAttributes('image', { src: url }) .run() onProgress?.(100) // Done return url } catch (error) { console.error('Image upload failed:', error) onError?.(error as Error) // Remove failed image from editor editor.chain().focus().deleteSelection().run() return null } } /** * Convert File to base64 string */ function fileToBase64(file: File): Promise<string> { return new Promise((resolve, reject) => { const reader = new FileReader() reader.onload = () => resolve(reader.result as string) reader.onerror = reject reader.readAsDataURL(file) }) } /** * Configure Image extension with upload handler * * Usage in useEditor(): * ```typescript * import { getImageExtensionWithUpload } from '@/lib/tiptap-image-upload' * * const editor = useEditor({ * extensions: [ * StarterKit, * getImageExtensionWithUpload(), * ], * }) * ``` */ export function getImageExtensionWithUpload() { return Image.extend({ addProseMirrorPlugins() { return [ // Add paste handler for images new Plugin({ key: new PluginKey('imageUpload'), props: { handlePaste(view, event) { const items = Array.from(event.clipboardData?.items || []) const editor = view.state as any // Get editor instance for (const item of items) { if (item.type.startsWith('image/')) { event.preventDefault() const file = item.getAsFile() if (file) { uploadImageToR2({ editor, file }) } return true } } return false }, handleDrop(view, event) { const files = Array.from(event.dataTransfer?.files || []) const editor = view.state as any for (const file of files) { if (file.type.startsWith('image/')) { event.preventDefault() uploadImageToR2({ editor, file }) return true } } return false }, }, }), ] }, }).configure({ inline: true, allowBase64: false, // ⚠️ Prevent base64 bloat in database HTMLAttributes: { class: 'rounded-lg max-w-full h-auto', }, }) } /** * Example: Editor component with image upload */ export function EditorWithImageUpload() { const [uploading, setUploading] = useState(false) const [progress, setProgress] = useState(0) const editor = useEditor({ extensions: [ StarterKit, getImageExtensionWithUpload(), ], immediatelyRender: false, }) const handleImageUpload = async (file: File) => { if (!editor) return setUploading(true) setProgress(0) await uploadImageToR2({ editor, file, onProgress: setProgress, onError: (error) => { alert(`Upload failed: ${error.message}`) }, }) setUploading(false) } return ( <div> {uploading && ( <div className="mb-2"> <div className="h-2 bg-muted rounded"> <div className="h-full bg-primary rounded transition-all" style={{ width: `${progress}%` }} /> </div> <p className="text-sm text-muted-foreground mt-1"> Uploading... {progress}% </p> </div> )} <EditorContent editor={editor} /> <div className="mt-2"> <input type="file" accept="image/*" onChange={(e) => { const file = e.target.files?.[0] if (file) handleImageUpload(file) }} className="text-sm" /> </div> </div> ) } // Required imports for Plugin example import { Plugin, PluginKey } from '@tiptap/pm/state' import { EditorContent, useEditor } from '@tiptap/react' import StarterKit from '@tiptap/starter-kit' import { useState } from 'react' ``` ### templates/package.json ```json { "name": "tiptap-project", "version": "1.0.0", "description": "Tiptap rich text editor project", "dependencies": { "@tiptap/react": "^3.11.1", "@tiptap/starter-kit": "^3.11.1", "@tiptap/pm": "^3.11.1", "@tiptap/extension-image": "^3.11.1", "@tiptap/extension-link": "^3.11.1", "@tiptap/extension-typography": "^3.11.1", "@tiptap/extension-placeholder": "^3.11.1", "@tiptap/extension-text-style": "^3.11.1", "@tiptap/extension-color": "^3.11.1", "react": "^19.0.0", "react-dom": "^19.0.0" }, "devDependencies": { "@tailwindcss/typography": "^0.5.15", "@tailwindcss/vite": "^4.1.14", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", "typescript": "^5.7.2", "vite": "^6.0.7" }, "optionalDependencies": { "@tiptap/markdown": "^3.11.1", "@tiptap/extension-collaboration": "^3.11.1", "@tiptap/extension-collaboration-cursor": "^3.11.1", "@tiptap/extension-task-list": "^3.11.1", "@tiptap/extension-task-item": "^3.11.1", "@tiptap/extension-table": "^3.11.1", "@tiptap/extension-table-row": "^3.11.1", "@tiptap/extension-table-cell": "^3.11.1", "@tiptap/extension-table-header": "^3.11.1", "@tiptap/extension-code-block-lowlight": "^3.11.1", "lowlight": "^3.1.0", "yjs": "^13.6.0", "y-websocket": "^2.0.4", "react-medium-image-zoom": "^5.2.0" }, "scripts": { "dev": "vite", "build": "vite build", "preview": "vite preview" }, "peerDependencies": { "react": "^19.0.0", "react-dom": "^19.0.0" } } ``` ### templates/minimal-tiptap-setup.sh ```bash #!/bin/bash # # shadcn Minimal Tiptap Component Installation # # This script installs the official shadcn minimal-tiptap component # which provides a fully-featured editor with: # - Comprehensive toolbar # - Image upload support # - Code block with syntax highlighting # - Link handling # - Typography extension # - Dark mode support # # Usage: # chmod +x minimal-tiptap-setup.sh # ./minimal-tiptap-setup.sh # set -e echo "📦 Installing shadcn minimal-tiptap component..." # Install the component via shadcn CLI npx shadcn@latest add https://raw.githubusercontent.com/Aslam97/shadcn-minimal-tiptap/main/registry/block-registry.json echo "📦 Installing required dependencies..." # Install Tiptap core packages npm install @tiptap/react @tiptap/starter-kit @tiptap/pm # Install additional extensions used by minimal-tiptap npm install @tiptap/extension-image \ @tiptap/extension-color \ @tiptap/extension-text-style \ @tiptap/extension-typography \ @tiptap/extension-code-block-lowlight \ lowlight \ react-medium-image-zoom echo "✅ Installation complete!" echo "" echo "📚 Usage Example:" echo "" echo "import { MinimalTiptapEditor } from '@/components/minimal-tiptap'" echo "" echo "function MyComponent() {" echo " const [content, setContent] = useState('')" echo " " echo " return (" echo " <MinimalTiptapEditor" echo " value={content}" echo " onChange={setContent}" echo " placeholder='Start writing...'" echo " />" echo " )" echo "}" echo "" echo "📖 Documentation: https://github.com/Aslam97/shadcn-minimal-tiptap" ``` ### references/tiptap-docs.md ```markdown # Tiptap Documentation Quick Reference **Last Updated**: 2025-11-29 This reference provides quick links to essential Tiptap documentation for common tasks. --- ## Official Documentation ### Core Docs - **Main Site**: https://tiptap.dev - **Installation (React)**: https://tiptap.dev/docs/editor/installation/react - **Getting Started**: https://tiptap.dev/docs/editor/getting-started/overview - **API Reference**: https://tiptap.dev/docs/editor/api/editor ### Key Concepts - **Extensions**: https://tiptap.dev/docs/editor/extensions - **Commands**: https://tiptap.dev/docs/editor/api/commands - **Nodes**: https://tiptap.dev/docs/editor/core-concepts/schema#nodes - **Marks**: https://tiptap.dev/docs/editor/core-concepts/schema#marks - **Collaborative Editing**: https://tiptap.dev/docs/editor/getting-started/collaborative-editing --- ## React Integration ### useEditor Hook - **API Docs**: https://tiptap.dev/docs/editor/api/editor - **Configuration Options**: https://tiptap.dev/docs/editor/api/editor#editor-configuration - **immediatelyRender**: https://tiptap.dev/docs/editor/api/editor#immediatelyrender ### Performance - **React Performance Guide**: https://tiptap.dev/docs/editor/getting-started/performance - **useEditorState Hook**: https://tiptap.dev/docs/editor/api/editor#useeditorstate --- ## Extensions Documentation ### StarterKit - **Overview**: https://tiptap.dev/docs/editor/extensions/functionality/starterkit - **Included Extensions**: - Bold: https://tiptap.dev/docs/editor/extensions/marks/bold - Italic: https://tiptap.dev/docs/editor/extensions/marks/italic - Strike: https://tiptap.dev/docs/editor/extensions/marks/strike - Code: https://tiptap.dev/docs/editor/extensions/marks/code - Heading: https://tiptap.dev/docs/editor/extensions/nodes/heading - Paragraph: https://tiptap.dev/docs/editor/extensions/nodes/paragraph - BulletList: https://tiptap.dev/docs/editor/extensions/nodes/bullet-list - OrderedList: https://tiptap.dev/docs/editor/extensions/nodes/ordered-list - Blockquote: https://tiptap.dev/docs/editor/extensions/nodes/blockquote - CodeBlock: https://tiptap.dev/docs/editor/extensions/nodes/code-block - HorizontalRule: https://tiptap.dev/docs/editor/extensions/nodes/horizontal-rule - Link: https://tiptap.dev/docs/editor/extensions/marks/link (NEW in v3) - Underline: https://tiptap.dev/docs/editor/extensions/marks/underline (NEW in v3) ### Popular Extensions - **Image**: https://tiptap.dev/docs/editor/extensions/nodes/image - **Link**: https://tiptap.dev/docs/editor/extensions/marks/link - **Typography**: https://tiptap.dev/docs/editor/extensions/marks/typography - **Placeholder**: https://tiptap.dev/docs/editor/extensions/functionality/placeholder - **Color**: https://tiptap.dev/docs/editor/extensions/marks/color - **TaskList**: https://tiptap.dev/docs/editor/extensions/nodes/task-list - **Table**: https://tiptap.dev/docs/editor/extensions/nodes/table - **CodeBlockLowlight**: https://tiptap.dev/docs/editor/extensions/nodes/code-block-lowlight ### Collaboration Extensions - **Collaboration**: https://tiptap.dev/docs/editor/extensions/functionality/collaboration - **CollaborationCursor**: https://tiptap.dev/docs/editor/extensions/functionality/collaboration-cursor --- ## Tailwind Integration ### shadcn minimal-tiptap - **GitHub**: https://github.com/Aslam97/shadcn-minimal-tiptap - **Live Demo**: https://shadcn-minimal-tiptap.vercel.app - **Installation**: https://github.com/Aslam97/shadcn-minimal-tiptap#installation ### Tailwind Typography - **Plugin Docs**: https://github.com/tailwindlabs/tailwindcss-typography - **Configuration**: https://github.com/tailwindlabs/tailwindcss-typography#configuration - **Customization**: https://github.com/tailwindlabs/tailwindcss-typography#customization --- ## Common Tasks ### Creating Custom Extensions - **Guide**: https://tiptap.dev/docs/editor/custom-extensions - **Extension API**: https://tiptap.dev/docs/editor/api/extension - **Node Extensions**: https://tiptap.dev/docs/editor/custom-extensions/create-a-node - **Mark Extensions**: https://tiptap.dev/docs/editor/custom-extensions/create-a-mark ### Working with Content - **Get Content**: https://tiptap.dev/docs/editor/api/editor#get-content - **Set Content**: https://tiptap.dev/docs/editor/api/editor#set-content - **JSON**: https://tiptap.dev/docs/editor/core-concepts/schema#json - **HTML**: https://tiptap.dev/docs/editor/api/utilities/html ### Styling - **Editor Props**: https://tiptap.dev/docs/editor/api/editor#editor-props - **CSS Classes**: https://tiptap.dev/docs/editor/getting-started/style-editor --- ## Migration Guides ### Upgrading to v3 - **Migration Guide**: https://tiptap.dev/docs/editor/migration/v2-to-v3 - **Breaking Changes**: Key changes include: - `immediatelyRender` now required for SSR - Link extension moved to StarterKit - Underline extension moved to StarterKit - New list handling (ListKeymap) ### From Other Editors - **From Slate**: Community comparison available - **From ProseMirror**: Tiptap is built on ProseMirror - **From Lexical**: Different architecture (consider use cases) --- ## Examples & Tutorials ### Official Examples - **Examples Repository**: https://github.com/ueberdosis/tiptap/tree/main/demos - **CodeSandbox**: https://codesandbox.io/examples/package/@tiptap/react - **Live Playground**: https://tiptap.dev/examples ### Community Resources - **shadcn-ui Discussions**: https://github.com/shadcn-ui/ui/discussions?discussions_q=tiptap - **Stack Overflow Tag**: https://stackoverflow.com/questions/tagged/tiptap - **Discord Community**: https://discord.gg/WtJ49jGshW --- ## GitHub - **Main Repository**: https://github.com/ueberdosis/tiptap - **Issues**: https://github.com/ueberdosis/tiptap/issues - **Releases**: https://github.com/ueberdosis/tiptap/releases - **Changelog**: https://github.com/ueberdosis/tiptap/blob/main/CHANGELOG.md --- ## Context7 MCP When using Context7 MCP for Tiptap documentation: **Library ID**: `tiptap/tiptap` **Example Query**: ``` resolve-library-id --library-name "tiptap" ``` **Common Queries**: - "How to configure immediatelyRender for SSR" - "Image extension configuration options" - "Collaborative editing setup with Y.js" - "Custom extension creation guide" --- ## Pro Extensions (Paid) For reference - require Tiptap Pro subscription: - **Content AI**: https://tiptap.dev/docs/content-ai - **Comments**: https://tiptap.dev/docs/comments - **FileHandler**: https://tiptap.dev/docs/file-handler - **Mathematics**: https://tiptap.dev/docs/mathematics - **TableOfContents**: https://tiptap.dev/docs/table-of-contents --- ## Quick Links by Use Case ### Blog/Article Editor 1. StarterKit configuration 2. Image extension with upload 3. Link extension 4. Typography extension 5. Tailwind prose styling ### Comment System 1. Minimal StarterKit (no headings) 2. Link extension 3. Placeholder 4. Compact prose styling ### Documentation Platform 1. Full StarterKit 2. Table extension 3. CodeBlockLowlight 4. TaskList 5. Typography ### Collaborative Editor 1. Collaboration extension 2. CollaborationCursor 3. Y.js integration 4. WebSocket provider --- **Last Verified**: 2025-11-29 **Tiptap Version**: 3.11.1 ``` ### references/common-errors.md ```markdown # Tiptap Common Errors & Solutions **Last Updated**: 2025-11-29 This reference documents common Tiptap errors with proven solutions. --- ## Error #1: SSR Hydration Mismatch ### Error Message ``` Warning: Prop `dangerouslySetInnerHTML` did not match. Server: "..." Client: "..." or SSR has been detected, please set `immediatelyRender` explicitly to `false` to avoid hydration mismatches. ``` ### Stack Trace Example ``` at EditorContent at Editor at App at ServerRoot ``` ### Why It Happens - Tiptap v3 defaults to `immediatelyRender: true` - This causes editor to render on server AND client - Server HTML doesn't match client-rendered HTML - Results in React hydration mismatch ### Solution Always set `immediatelyRender: false` in Next.js/SSR apps: ```typescript const editor = useEditor({ extensions: [StarterKit], immediatelyRender: false, // ⚠️ Required for SSR // ... other options }) ``` ### References - GitHub Issue: https://github.com/ueberdosis/tiptap/issues/5856 - GitHub Issue: https://github.com/ueberdosis/tiptap/issues/5602 - Official Docs: https://tiptap.dev/docs/editor/api/editor#immediatelyrender ### Prevention - Add to editor configuration checklist - Use base-editor.tsx template (includes fix) - Test with `next build` before deploying --- ## Error #2: Headings/Lists Render Unstyled ### Symptoms - Formatted content looks like plain text - No visual difference between H1, H2, paragraph - Lists show bullets but no indentation - Links not colored/underlined ### Why It Happens - Missing `@tailwindcss/typography` plugin - No `prose` classes applied to container - Custom CSS not loaded ### Solution A: Install Tailwind Typography (Recommended) ```bash npm install @tailwindcss/typography ``` ```typescript // tailwind.config.ts import typography from '@tailwindcss/typography' export default { plugins: [typography], } ``` ```tsx // Apply prose classes <EditorContent editor={editor} className="prose prose-sm dark:prose-invert max-w-none" /> ``` ### Solution B: Custom CSS Use `templates/tiptap-prose.css` from this skill: ```tsx import './tiptap-prose.css' <div className="tiptap"> <EditorContent editor={editor} /> </div> ``` ### References - shadcn Discussion: https://github.com/shadcn-ui/ui/discussions/1729 - Tailwind Typography: https://github.com/tailwindlabs/tailwindcss-typography --- ## Error #3: Performance Issues / Editor Lags ### Symptoms - Editor lags when typing - Slow response to formatting commands - High CPU usage during editing - UI freezes on large documents ### Why It Happens - `useEditor()` re-renders component on every change - Large document tree causes expensive re-renders - Extensions not memoized - Too many extensions loaded ### Solution A: Use useEditorState for Read-Only ```typescript import { useEditor, useEditorState } from '@tiptap/react' function DisplayEditor({ content }: { content: string }) { const editor = useEditor({ extensions: [StarterKit], content, editable: false, immediatelyRender: false, }) // Only subscribe to specific state changes const { isFocused } = useEditorState({ editor, selector: (ctx) => ({ isFocused: ctx.editor.isFocused }), }) return <EditorContent editor={editor} /> } ``` ### Solution B: Memoize Configuration ```typescript import { useMemo } from 'react' function Editor() { const extensions = useMemo(() => [ StarterKit, Image, Link, ], []) const editor = useEditor({ extensions, immediatelyRender: false, }) return <EditorContent editor={editor} /> } ``` ### Solution C: Lazy Load Extensions Only load extensions when needed: ```typescript const extensions = [ StarterKit, // Only add Table if user needs tables ...(needsTables ? [Table, TableRow, TableCell] : []), ] ``` ### References - Performance Docs: https://tiptap.dev/docs/editor/getting-started/performance - useEditorState: https://tiptap.dev/docs/editor/api/editor#useeditorstate --- ## Error #4: Image Upload Base64 Bloat ### Symptoms - Database payload size in megabytes - Slow saves/loads - Database storage fills quickly - JSON serialization takes long time ### Why It Happens - Default Image extension allows base64 - Pasted images convert to base64 automatically - No upload handler configured ### Solution Set `allowBase64: false` and implement upload handler: ```typescript import Image from '@tiptap/extension-image' const editor = useEditor({ extensions: [ StarterKit, Image.configure({ inline: true, allowBase64: false, // ⚠️ Prevent base64 bloat }), ], immediatelyRender: false, }) // Implement upload handler // See templates/image-upload-r2.tsx for full example ``` ### Pattern 1. Insert base64 preview for immediate feedback 2. Upload to R2/S3 in background 3. Replace base64 with permanent URL 4. Store only URL in database ### References - Image Extension Docs: https://tiptap.dev/docs/editor/extensions/nodes/image - Upload Template: `templates/image-upload-r2.tsx` --- ## Error #5: Build Fails in Create React App ### Error Message ``` Module not found: Error: Can't resolve 'jsx-runtime' or Cannot find module '@tiptap/react' ``` ### Why It Happens - Tiptap v3 uses modern module structure - Create React App (CRA) doesn't support it - webpack configuration incompatibility ### Solution Switch to Vite (recommended): ```bash # Create new Vite project npm create vite@latest my-project -- --template react-ts # Install Tiptap npm install @tiptap/react @tiptap/starter-kit @tiptap/pm # Copy your components ``` ### Alternative: Downgrade to Tiptap v2 ```bash npm install @tiptap/[email protected] @tiptap/[email protected] ``` ⚠️ Not recommended - v2 missing new features ### References - GitHub Issue: https://github.com/ueberdosis/tiptap/issues/6812 - Vite Migration: https://vitejs.dev/guide/migration-from-cra.html --- ## Error #6: TypeScript Type Errors ### Error Message ``` Type 'Editor | null' is not assignable to type 'Editor' or Property 'chain' does not exist on type 'null' ``` ### Why It Happens - `useEditor()` returns `Editor | null` - Editor is null during initial render - TypeScript strict null checks ### Solution Always check for null: ```typescript const editor = useEditor({ ... }) if (!editor) { return null // or loading spinner } // Now safe to use editor editor.chain().focus().toggleBold().run() ``` ### Pattern for Event Handlers ```typescript <button onClick={() => editor?.chain().focus().toggleBold().run()} disabled={!editor} > Bold </button> ``` --- ## Error #7: Extensions Not Working ### Symptoms - Extension installed but commands don't work - Extension features not visible - No error messages ### Why It Happens - Extension not added to `extensions` array - Extension loaded in wrong order - Extension configuration incorrect ### Solution ```typescript import StarterKit from '@tiptap/starter-kit' import Image from '@tiptap/extension-image' // ← Must import const editor = useEditor({ extensions: [ StarterKit, Image, // ← Must add to array ], }) ``` ### Check Extension is Active ```typescript if (editor.isActive('image')) { console.log('Image extension loaded') } ``` ### Extension Order Matters Some extensions depend on others: ```typescript const editor = useEditor({ extensions: [ // Base extensions first Document, Paragraph, Text, // Then marks/nodes that depend on them Bold, Image, ], }) ``` --- ## Error #8: Content Not Updating ### Symptoms - Editor content doesn't reflect prop changes - `setContent` doesn't work - Controlled component issues ### Why It Happens - Editor content not synced with React state - Missing `useEffect` to sync content - Calling `setContent` during render ### Solution Sync content in `useEffect`: ```typescript import { useEffect } from 'react' function Editor({ content }: { content: string }) { const editor = useEditor({ extensions: [StarterKit], immediatelyRender: false, }) // Sync external content changes useEffect(() => { if (editor && content !== editor.getHTML()) { editor.commands.setContent(content) } }, [content, editor]) return <EditorContent editor={editor} /> } ``` --- ## Error #9: Placeholder Not Showing ### Symptoms - Placeholder text not visible - Empty editor looks blank ### Why It Happens - Placeholder extension not installed - CSS for placeholder missing - `editorProps` not configured ### Solution ```typescript import Placeholder from '@tiptap/extension-placeholder' const editor = useEditor({ extensions: [ StarterKit, Placeholder.configure({ placeholder: 'Start writing...', }), ], editorProps: { attributes: { 'data-placeholder': 'Start writing...', }, }, }) ``` CSS (in tiptap-prose.css): ```css .tiptap p.is-editor-empty:first-child::before { content: attr(data-placeholder); color: var(--muted-foreground); float: left; height: 0; pointer-events: none; } ``` --- ## Error #10: Collaborative Editing Conflicts ### Symptoms - Content overwrites between users - Cursor positions wrong - Undo/redo breaks ### Why It Happens - Local history conflicts with Y.js - Multiple users editing same node - Network latency ### Solution Disable local history for collaboration: ```typescript import Collaboration from '@tiptap/extension-collaboration' const editor = useEditor({ extensions: [ StarterKit.configure({ history: false, // ⚠️ Disable for collaboration }), Collaboration.configure({ document: ydoc, }), ], }) ``` --- ## Debugging Tips ### Enable Tiptap Debug Mode ```typescript const editor = useEditor({ extensions: [StarterKit], enablePasteRules: true, enableInputRules: true, onBeforeCreate: ({ editor }) => { console.log('Editor creating:', editor) }, onCreate: ({ editor }) => { console.log('Editor created:', editor) }, onUpdate: ({ editor }) => { console.log('Editor updated:', editor.getJSON()) }, }) ``` ### Inspect Editor State ```typescript console.log(editor.getJSON()) // Current content as JSON console.log(editor.getHTML()) // Current content as HTML console.log(editor.state) // ProseMirror state console.log(editor.extensionManager.extensions) // Loaded extensions ``` ### Common Console Checks ```typescript // Is extension loaded? console.log(editor.extensionManager.extensions.map(e => e.name)) // Is editor editable? console.log(editor.isEditable) // What's selected? console.log(editor.state.selection) // Active marks/nodes? console.log(editor.getAttributes('heading')) // { level: 2 } ``` --- **Last Verified**: 2025-11-29 **Tiptap Version**: 3.11.1 ``` ### references/extension-catalog.md ```markdown # Tiptap Extension Catalog **Last Updated**: 2025-11-29 **Last Verified**: 2025-11-29 (package versions, markdown API, Image resize option) Comprehensive catalog of official and community Tiptap extensions. --- ## Official Extensions (Free) ### Included in StarterKit **Marks** (formatting that can be applied to text): - **Bold** - `@tiptap/extension-bold` - Bold text - **Italic** - `@tiptap/extension-italic` - Italic text - **Strike** - `@tiptap/extension-strike` - Strikethrough text - **Code** - `@tiptap/extension-code` - Inline code formatting - **Link** - `@tiptap/extension-link` - URL links (NEW in v3) - **Underline** - `@tiptap/extension-underline` - Underline text (NEW in v3) **Nodes** (content blocks): - **Document** - `@tiptap/extension-document` - Root document - **Paragraph** - `@tiptap/extension-paragraph` - Paragraph blocks - **Text** - `@tiptap/extension-text` - Text content - **Heading** - `@tiptap/extension-heading` - Headings (H1-H6) - **BulletList** - `@tiptap/extension-bullet-list` - Unordered lists - **OrderedList** - `@tiptap/extension-ordered-list` - Numbered lists - **ListItem** - `@tiptap/extension-list-item` - List item nodes - **Blockquote** - `@tiptap/extension-blockquote` - Quote blocks - **CodeBlock** - `@tiptap/extension-code-block` - Code blocks - **HorizontalRule** - `@tiptap/extension-horizontal-rule` - Horizontal dividers - **HardBreak** - `@tiptap/extension-hard-break` - Line breaks **Functionality**: - **History** - `@tiptap/extension-history` - Undo/redo - **Dropcursor** - `@tiptap/extension-dropcursor` - Drop cursor indicator - **Gapcursor** - `@tiptap/extension-gapcursor` - Gap cursor for empty blocks - **ListKeymap** - Keyboard shortcuts for lists (NEW in v3) - **TrailingNode** - Ensures trailing paragraph (NEW in v3) ### Not in StarterKit (Install Separately) **Media**: - **Image** - `@tiptap/extension-image` - Images with src attribute ```typescript import Image from '@tiptap/extension-image' Image.configure({ inline: true, allowBase64: false, resize: { enabled: true, // NEW: Drag-and-drop resizing directions: ['top-right', 'bottom-right', 'bottom-left', 'top-left'], minWidth: 100, minHeight: 100, alwaysPreserveAspectRatio: true, }, HTMLAttributes: { class: 'rounded-lg', }, }) ``` **Text Styling**: - **TextStyle** - `@tiptap/extension-text-style` - Text style container - **Color** - `@tiptap/extension-color` - Text color - **Highlight** - `@tiptap/extension-highlight` - Text highlighting - **FontFamily** - `@tiptap/extension-font-family` - Font family control - **Subscript** - `@tiptap/extension-subscript` - Subscript text - **Superscript** - `@tiptap/extension-superscript` - Superscript text **Content**: - **Typography** - `@tiptap/extension-typography` - Smart quotes, dashes, ellipsis ```typescript import Typography from '@tiptap/extension-typography' // Converts: // (c) → © // -> → → // ... → … // "text" → "text" ``` - **Placeholder** - `@tiptap/extension-placeholder` - Placeholder text ```typescript import Placeholder from '@tiptap/extension-placeholder' Placeholder.configure({ placeholder: 'Start writing...', emptyEditorClass: 'is-editor-empty', }) ``` **Tables**: - **Table** - `@tiptap/extension-table` - Table container - **TableRow** - `@tiptap/extension-table-row` - Table rows - **TableCell** - `@tiptap/extension-table-cell` - Table cells - **TableHeader** - `@tiptap/extension-table-header` - Table headers ```typescript import Table from '@tiptap/extension-table' import TableRow from '@tiptap/extension-table-row' import TableCell from '@tiptap/extension-table-cell' import TableHeader from '@tiptap/extension-table-header' const extensions = [ Table.configure({ resizable: true, }), TableRow, TableCell, TableHeader, ] ``` **Task Lists**: - **TaskList** - `@tiptap/extension-task-list` - Task list container - **TaskItem** - `@tiptap/extension-task-item` - Individual tasks ```typescript import TaskList from '@tiptap/extension-task-list' import TaskItem from '@tiptap/extension-task-item' const extensions = [ TaskList, TaskItem.configure({ nested: true, }), ] ``` **Code Blocks**: - **CodeBlockLowlight** - `@tiptap/extension-code-block-lowlight` - Syntax highlighted code ```typescript import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight' import { common, createLowlight } from 'lowlight' const lowlight = createLowlight(common) CodeBlockLowlight.configure({ lowlight, }) ``` **Collaboration**: - **Collaboration** - `@tiptap/extension-collaboration` - Real-time collaboration - **CollaborationCursor** - `@tiptap/extension-collaboration-cursor` - User cursors ```typescript import Collaboration from '@tiptap/extension-collaboration' import CollaborationCursor from '@tiptap/extension-collaboration-cursor' import * as Y from 'yjs' const ydoc = new Y.Doc() const extensions = [ Collaboration.configure({ document: ydoc, }), CollaborationCursor.configure({ provider, user: { name: 'John Doe', color: '#3b82f6', }, }), ] ``` **Utilities**: - **CharacterCount** - `@tiptap/extension-character-count` - Character/word count - **Focus** - `@tiptap/extension-focus` - Focus management - **TextAlign** - `@tiptap/extension-text-align` - Text alignment --- ## Official Pro Extensions (Paid) Require Tiptap Pro subscription. **AI-Powered**: - **Content AI** - AI writing assistant - **AI Image** - AI image generation **Productivity**: - **Comments** - Inline commenting - **FileHandler** - Drag & drop file uploads - **Mathematics** - Math equations (LaTeX) - **TableOfContents** - Auto table of contents - **UniqueID** - Unique IDs for nodes **More**: https://tiptap.dev/pricing --- ## Community Extensions Popular third-party extensions: ### Rich Media **tiptap-extension-global-drag-handle** - Drag handle for all blocks (Notion-like) - GitHub: https://github.com/johanneskoch94/tiptap-extension-global-drag-handle **@tiptap-pro/extension-emoji** - Emoji picker integration - GitHub: https://github.com/ueberdosis/tiptap-demos **tiptap-youtube** - YouTube video embeds - npm: `tiptap-youtube` **tiptap-audio** - Audio player embeds - npm: `tiptap-audio` ### Formatting **tiptap-indent** - Text indentation - npm: `@joeattardi/tiptap-indent` **tiptap-text-direction** - RTL/LTR text direction - npm: `tiptap-text-direction` **tiptap-margin** - Block margin control - npm: `tiptap-margin` ### Interactive **tiptap-mention** - @mentions autocomplete - GitHub: https://github.com/ueberdosis/tiptap/tree/main/packages/extension-mention **tiptap-slash-command** - Slash command menu (/) - npm: `tiptap-slash-command` **tiptap-extension-details-summary** - Collapsible details/summary blocks - npm: `tiptap-extension-details-summary` ### Markdown **@tiptap/markdown** (Official, Recommended) - Bidirectional markdown parser and serializer - npm: `@tiptap/[email protected]` - Import: `import { Markdown } from '@tiptap/markdown'` - Released: October 15, 2025 (v3.7.0) - Status: Beta (API stable but may evolve) - Uses MarkedJS for CommonMark-compliant parsing - Docs: https://tiptap.dev/docs/editor/markdown ```typescript import { Editor } from '@tiptap/core' import StarterKit from '@tiptap/starter-kit' import { Markdown } from '@tiptap/markdown' const editor = new Editor({ extensions: [StarterKit, Markdown], content: '# Hello World\n\nThis is **Markdown**!', contentType: 'markdown', // ⚠️ CRITICAL: Must specify }) // Get markdown output const markdown = editor.getMarkdown() // Insert markdown editor.commands.setContent('## New', { contentType: 'markdown' }) ``` **tiptap-markdown** (Community, Legacy) - Community markdown package (pre-official) - npm: `[email protected]` - GitHub: https://github.com/aguingand/tiptap-markdown - Status: Maintainer not planning v1, recommends official package - Recommendation: Use official `@tiptap/markdown` instead --- ## Extension Development ### Creating Custom Extensions **Node Extension Template**: ```typescript import { Node } from '@tiptap/core' export 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 }) }, } }, }) ``` **Mark Extension Template**: ```typescript import { Mark } from '@tiptap/core' export const CustomMark = Mark.create({ name: 'customMark', parseHTML() { return [ { tag: 'span[data-custom]', }, ] }, renderHTML({ HTMLAttributes }) { return ['span', { 'data-custom': '', ...HTMLAttributes }, 0] }, addCommands() { return { toggleCustomMark: () => ({ commands }) => { return commands.toggleMark(this.name) }, } }, }) ``` **Extension Extension Template**: ```typescript import { Extension } from '@tiptap/core' export const CustomExtension = Extension.create({ name: 'customExtension', addOptions() { return { // Custom options } }, addCommands() { return { // Custom commands } }, addKeyboardShortcuts() { return { 'Mod-Shift-x': () => this.editor.commands.toggleCustomMark(), } }, }) ``` ### Resources - **Custom Extensions Guide**: https://tiptap.dev/docs/editor/custom-extensions - **Extension API**: https://tiptap.dev/docs/editor/api/extension - **Examples**: https://github.com/ueberdosis/tiptap/tree/main/demos --- ## Extension Recommendations by Use Case ### Blog Editor - StarterKit - Image - Link - Typography - Placeholder - CharacterCount ### Comment System - StarterKit (minimal config) - Link - Typography - Placeholder - CharacterCount (optional) ### Documentation - StarterKit - Image - Table (+ TableRow, TableCell, TableHeader) - CodeBlockLowlight - TaskList (+ TaskItem) - Typography - TableOfContents (Pro) ### Notion-like Editor - StarterKit - Image - Table - TaskList - CodeBlockLowlight - Collaboration (+ CollaborationCursor) - Slash commands (community) - Drag handle (community) ### Form Input - StarterKit (minimal) - Placeholder - CharacterCount - TextAlign (optional) --- ## Installation Quick Reference ```bash # Core npm install @tiptap/react @tiptap/starter-kit @tiptap/pm # Media npm install @tiptap/extension-image # Text Styling npm install @tiptap/extension-text-style @tiptap/extension-color @tiptap/extension-highlight # Content npm install @tiptap/extension-typography @tiptap/extension-placeholder # Tables npm install @tiptap/extension-table @tiptap/extension-table-row @tiptap/extension-table-cell @tiptap/extension-table-header # Task Lists npm install @tiptap/extension-task-list @tiptap/extension-task-item # Code Blocks with Syntax Highlighting npm install @tiptap/extension-code-block-lowlight lowlight # Collaboration npm install @tiptap/extension-collaboration @tiptap/extension-collaboration-cursor yjs # Utilities npm install @tiptap/extension-character-count @tiptap/extension-focus @tiptap/extension-text-align ``` --- **Last Verified**: 2025-11-29 **Tiptap Version**: 3.11.1 ```