Main Site ↗

tiptap

by jezweb67224GitHub

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.

Unlock Deep Analysis

Use AI to visualize the workflow and generate a realistic output preview for this skill.

Powered by Fastest LLM

Target Audience

React developers building rich text editors for blogs, comment systems, or collaborative apps using Tiptap with Tailwind CSS

10/10Security

Low security risk, safe to use

9
Clarity
9
Practicality
8
Quality
8
Maintainability
7
Innovation
Development
Compatible Agents
Claude Code
Claude Code
~/.claude/skills/
Codex CLI
Codex CLI
~/.codex/skills/
Gemini CLI
Gemini CLI
~/.gemini/skills/
O
OpenCode
~/.opencode/skills/
O
OpenClaw
~/.openclaw/skills/
GitHub Copilot
GitHub Copilot
~/.copilot/skills/
Cursor
Cursor
~/.cursor/skills/
W
Windsurf
~/.codeium/windsurf/skills/
C
Cline
~/.cline/skills/
R
Roo Code
~/.roo/skills/
K
Kiro
~/.kiro/skills/
J
Junie
~/.junie/skills/
A
Augment Code
~/.augment/skills/
W
Warp
~/.warp/skills/
G
Goose
~/.config/goose/skills/
SKILL.md

Tiptap Rich Text Editor

Status: Production Ready Last Updated: 2026-01-21 Dependencies: React 19+, Tailwind v4, shadcn/ui (recommended) Latest Versions: @tiptap/react@3.16.0, @tiptap/starter-kit@3.16.0, @tiptap/pm@3.16.0 (verified 2026-01-21)


Quick Start (5 Minutes)

1. Install Dependencies

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

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)

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


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?

  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

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

/**
 * 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

/**
 * 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

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

{
  "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

#!/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

# 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

# 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

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)

npm install @tailwindcss/typography
// tailwind.config.ts
import typography from '@tailwindcss/typography'

export default {
  plugins: [typography],
}
// 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:

import './tiptap-prose.css'

<div className="tiptap">
  <EditorContent editor={editor} />
</div>

References


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

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

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:

const extensions = [
  StarterKit,
  // Only add Table if user needs tables
  ...(needsTables ? [Table, TableRow, TableCell] : []),
]

References


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:

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


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):

# 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

npm install @tiptap/react@2.27.1 @tiptap/starter-kit@2.27.1

āš ļø Not recommended - v2 missing new features

References


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:

const editor = useEditor({ ... })

if (!editor) {
  return null // or loading spinner
}

// Now safe to use editor
editor.chain().focus().toggleBold().run()

Pattern for Event Handlers

<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

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

if (editor.isActive('image')) {
  console.log('Image extension loaded')
}

Extension Order Matters

Some extensions depend on others:

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:

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

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):

.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:

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

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

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

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

    import Typography from '@tiptap/extension-typography'
    
    // Converts:
    // (c) → Ā©
    // -> → →
    // ... → …
    // "text" → "text"
    
  • Placeholder - @tiptap/extension-placeholder - Placeholder text

    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
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
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
    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
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

@tiptap-pro/extension-emoji

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

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/markdown@3.11.1
  • 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
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: tiptap-markdown@0.9.0
  • 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:

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:

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:

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


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

# 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

Source: https://github.com/jezweb/claude-skills#skills-tiptap

Content curated from original sources, copyright belongs to authors

Grade A
8.3AI Score
Best Practices
Checking...
Try this Skill

User Rating

USER RATING

0UP
0DOWN
Loading files...

WORKS WITH

Claude Code
Claude
Codex CLI
Codex
Gemini CLI
Gemini
O
OpenCode
O
OpenClaw
GitHub Copilot
Copilot
Cursor
Cursor
W
Windsurf
C
Cline
R
Roo
K
Kiro
J
Junie
A
Augment
W
Warp
G
Goose