Prerequisites
- React 18 or 19
- A shadcn/ui setup with Tailwind CSS v4
- Tiptap v2 or v3 (
>=2.11.5 <4)
Choose your editor
editorcn offers two editor packages. Pick the one that fits your use case:
- @editorcn/editor — a classic toolbar-style rich text editor
- @editorcn/block-editor — a Notion-style block editor with slash commands
For rendering saved content read-only (blog posts, previews, chat history), see the Static Renderer.
Both packages can be installed either via npm or via the shadcn registry:
- shadcn registry (recommended) copies the component source directly into your project (under
components/), so you own and can customize the code. This is the preferred method since it gives you full control over styling and behavior. - npm installs the package as a regular dependency from the npm registry. Use this if you'd rather not vendor the component source into your repo.
Setup
Since the shadcn registry is the recommended install method, add the editorcn registry to your components.json first (skip this if you're installing via npm only):
{
"$schema": "https://ui.shadcn.com/schema/registry.json",
"registries": {
"@editorcn": "https://editorcn.vercel.app/r/{name}.json"
}
}Option 1: @editorcn/editor (Toolbar-style)
Install via the shadcn registry (recommended):
$ pnpm dlx shadcn@latest add @editorcn/editor
Or install via npm:
$ pnpm add @editorcn/editor @tiptap/react @tiptap/pm @tiptap/starter-kit @tiptap/extension-link @tiptap/extension-underline @tiptap/extension-character-count @tiptap/extension-text-style @tiptap/extension-color @tiptap/extension-highlight
Import the styles:
// Your shadcn globals
import "@/app/globals.css";
// Editor component styles
import "@/components/editor/style.css";Order matters: Import your shadcn globals first, then the editor styles.
"use client";
import { useEditor } from "@tiptap/react";
import { StarterKit } from "@tiptap/starter-kit";
import { Underline } from "@tiptap/extension-underline";
import { TextAlign } from "@tiptap/extension-text-align";
import { Placeholder } from "@tiptap/extension-placeholder";
import { TextStyle } from "@tiptap/extension-text-style";
import { Color } from "@tiptap/extension-color";
import { Highlight } from "@tiptap/extension-highlight";
import { CharacterCount } from "@tiptap/extension-character-count";
import { RichTextEditor, Link } from "@/components/editor";
import "@/components/editor/style.css";
export function MyEditor() {
const editor = useEditor({
immediatelyRender: false,
shouldRerenderOnTransaction: false,
extensions: [
StarterKit.configure({ heading: { levels: [1, 2, 3, 4, 5, 6] } }),
Link,
Underline,
TextAlign.configure({ types: ["heading", "paragraph"] }),
Placeholder.configure({ placeholder: "Start typing..." }),
CharacterCount,
TextStyle,
Color,
Highlight.configure({ multicolor: true }),
],
content: "<p>Start typing...</p>",
});
return (
<RichTextEditor editor={editor}>
<RichTextEditor.Toolbar sticky>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Bold />
<RichTextEditor.Italic />
<RichTextEditor.Underline />
<RichTextEditor.Strikethrough />
<RichTextEditor.Code />
<RichTextEditor.ClearFormatting />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.H1 />
<RichTextEditor.H2 />
<RichTextEditor.H3 />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.BulletList />
<RichTextEditor.OrderedList />
<RichTextEditor.Blockquote />
<RichTextEditor.Hr />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.AlignLeft />
<RichTextEditor.AlignCenter />
<RichTextEditor.AlignRight />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Link />
<RichTextEditor.Unlink />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Undo />
<RichTextEditor.Redo />
</RichTextEditor.ControlsGroup>
</RichTextEditor.Toolbar>
<RichTextEditor.BubbleMenu editor={editor} />
<RichTextEditor.Content />
<RichTextEditor.Footer showWordCount />
</RichTextEditor>
);
}Option 2: @editorcn/block-editor (Blocks-style)
Install via the shadcn registry (recommended):
$ pnpm dlx shadcn@latest add @editorcn/block-editor
Or install via npm:
$ pnpm add @editorcn/block-editor @tiptap/react @tiptap/pm @tiptap/starter-kit @tiptap/suggestion @tiptap/extension-drag-handle-react @tiptap/extension-link @tiptap/extension-underline @tiptap/extension-placeholder @tiptap/extension-text-style @tiptap/extension-color @tiptap/extension-highlight
Import the styles:
import "@/components/block-editor/style.css";"use client";
import { useEditor } from "@tiptap/react";
import { StarterKit } from "@tiptap/starter-kit";
import { Underline } from "@tiptap/extension-underline";
import { Placeholder } from "@tiptap/extension-placeholder";
import {
BlockEditor,
SlashCommand,
defaultSlashCommandItems,
getSlashCommandSuggestion,
} from "@/components/block-editor";
import "@/components/block-editor/style.css";
export function MyEditor() {
const editor = useEditor({
immediatelyRender: false,
shouldRerenderOnTransaction: false,
extensions: [
StarterKit.configure({ heading: { levels: [1, 2, 3] } }),
Placeholder.configure({ placeholder: "Type / for commands..." }),
Underline,
SlashCommand.configure({
suggestion: getSlashCommandSuggestion(),
}),
],
});
return <BlockEditor editor={editor} />;
}See the @editorcn/editor docs or @editorcn/block-editor docs for detailed API references.
Peer dependencies
All @tiptap/* packages are peer dependencies — you must install them in your project. This ensures a single copy of Tiptap's types and avoids "duplicate instance" TypeScript errors.
This applies regardless of install method: npm installs list them explicitly as shown above, and the shadcn registry installer will prompt you to add any peer dependencies it detects are missing.
Keep Tiptap versions in sync
Every @tiptap/* package requires the exact same version of the others. For example, @tiptap/extension-mention@3.32.0 only works with @tiptap/core@3.32.0.
The shadcn CLI installs the newest Tiptap version in the range an item lists. If your project is on an older Tiptap release, that newer package won't match the ones you already have, and npm stops with an error like this:
npm error code ERESOLVE
npm error Conflicting peer dependency: @tiptap/pm@3.32.0To fix it, upgrade all your @tiptap/* packages to the same version, then run the shadcn add command again:
$ pnpm add @tiptap/core@latest @tiptap/pm@latest @tiptap/react@latest @tiptap/starter-kit@latest
Include every @tiptap/* package listed in your package.json. If npm still reports a conflict, your lockfile is holding on to the old versions: delete node_modules and package-lock.json and run npm install again. With pnpm, run pnpm up "@tiptap/*" --latest.
Scaffold production-ready Python, Go, and Rust projects from one CLI. Open source, zero lock-in.