Rtecn
@rtecn/block-editor

BlockEditor

Notion-style block-based rich text editor with slash commands and bubble menu.

Block Editor

Package: @rtecn/block-editor Import: import { BlockEditor } from "@rtecn/block-editor" Description: Notion-style block-based editor with slash commands, drag handles, and a context-aware bubble menu for inline formatting.

Installation

pnpm add @rtecn/block-editor @tiptap/react @tiptap/pm @tiptap/starter-kit
npm install @rtecn/block-editor @tiptap/react @tiptap/pm @tiptap/starter-kit

After installation, import the editor styles at the root of your application:

// Your shadcn globals
import "@rtecn/ui/globals.css";

// Block editor styles
import "@rtecn/block-editor/style.css";

Usage

"use client";

import { useEditor } from "@tiptap/react";
import StarterKit from "@tiptap/starter-kit";
import Placeholder from "@tiptap/extension-placeholder";
import Underline from "@tiptap/extension-underline";
import TaskList from "@tiptap/extension-task-list";
import TaskItem from "@tiptap/extension-task-item";
import {
  BlockEditor,
  SlashCommand,
  getSlashCommandSuggestion,
} from "@rtecn/block-editor";

function MyBlockEditor() {
  const editor = useEditor({
    immediatelyRender: false,
    extensions: [
      StarterKit.configure({
        heading: { levels: [1, 2, 3] },
      }),
      Placeholder.configure({ placeholder: "Type / for commands..." }),
      Underline,
      TaskList,
      TaskItem.configure({ nested: true }),
      SlashCommand.configure({
        suggestion: getSlashCommandSuggestion(),
      }),
    ],
  });

  return <BlockEditor editor={editor} />;
}

BlockEditor (Root)

The root wrapper that provides editor context. Renders the full editor UI by default (drag handle, block actions, bubble menu, and content area) or accepts custom children.

Props

PropTypeDefaultDescription
editorEditor | nullThe Tiptap editor instance
childrenReactNodeOptional custom children to replace the default layout
classNamestringAdditional CSS classes
labelsPartial<BlockEditorLabels>DEFAULT_BLOCK_EDITOR_LABELSOverride default slash command labels

Labels

KeyDefaultDescription
paragraphLabel"Text"Label for paragraph node
headingLabel"Heading"Label for heading nodes
bulletListLabel"Bullet list"Label for bullet list
orderedListLabel"Numbered list"Label for ordered list
taskListLabel"To-do list"Label for task list
blockquoteLabel"Quote"Label for blockquote
codeBlockLabel"Code"Label for code block
dividerLabel"Divider"Label for divider

BlockEditor.Content

Renders the editor content area with the drag handle and block actions. Use this when providing custom children to the root.

<BlockEditor editor={editor}>
  <BlockEditor.Content />
</BlockEditor>

BlockEditor.BubbleMenu

A standalone bubble menu component that can be used outside the BlockEditor root. Useful if you want to customize the bubble menu position or behavior.

<BlockEditor editor={editor}>
  <BlockEditor.Content />
  <BlockEditor.BubbleMenu />
</BlockEditor>

Features

Slash command menu

Type / to open the slash command menu. The menu shows available block types. Navigate with arrow keys and press Enter to insert.

Available commands:

CommandDescription
TextPlain paragraph
Heading 1/2/3Section headings
Bullet ListUnordered list
Numbered ListOrdered list
Task ListChecklist with checkboxes
QuoteBlockquote
CodeCode block with syntax highlighting (requires lowlight)
DividerHorizontal rule
ImageInsert an image via URL
TableInsert a 3×3 table with header row

Custom slash command items

import {
  BlockEditor,
  SlashCommand,
  defaultSlashCommandItems,
  getSlashCommandSuggestion,
} from "@rtecn/block-editor";

const myItems = [
  ...defaultSlashCommandItems,
  {
    id: "custom",
    title: "Custom",
    description: "A custom command",
    keywords: ["custom"],
    command: ({ editor, range }) => {
      editor.chain().focus().deleteRange(range).insertContent("Hello!").run();
    },
  },
];

const editor = useEditor({
  extensions: [
    StarterKit,
    SlashCommand.configure({
      suggestion: getSlashCommandSuggestion(myItems),
    }),
  ],
});

Bubble menu

Select text to see the floating bubble menu with:

  • Node type selector — Switch between paragraph, headings, lists, etc.
  • Inline formatting — Bold, Italic, Underline, Strike, Code
  • Link insertion/removal — Add or remove links
  • Text alignment — Left, Center, Right

The bubble menu automatically positions itself near the selection and is styled with your theme's --popover tokens.

Block actions

Click the drag handle (grip icon) on the left side of any block to access a dropdown with:

  • Duplicate — Duplicate the current block
  • Delete — Delete the current block

Drag handle

Drag blocks to reorder them using the grip icon on the left. The drag handle uses @tiptap/extension-drag-handle-react.

Extensions

The BlockEditor uses these Tiptap extensions internally (they must be installed as peer dependencies):

ExtensionPurposeRequired
@tiptap/starter-kitCore editor functionalityYes
@tiptap/pmProseMirror runtimeYes
@tiptap/reactReact bindingsYes

These extensions are bundled as direct dependencies:

ExtensionPurpose
@tiptap/extension-drag-handleBlock drag handle logic
@tiptap/extension-drag-handle-reactReact drag handle UI
@tiptap/extension-imageImage block
@tiptap/extension-tableTable block
@tiptap/extension-table-rowTable row
@tiptap/extension-table-cellTable cell
@tiptap/extension-table-headerTable header
@tiptap/suggestionSlash command suggestion engine
@tiptap/extension-code-block-lowlight(optional) Syntax highlighting

Syntax highlighting

To enable syntax highlighting in code blocks, install lowlight and pass it to @tiptap/extension-code-block-lowlight:

pnpm add lowlight @tiptap/extension-code-block-lowlight
import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
import { common, createLowlight } from "lowlight";

const lowlight = createLowlight(common);

const editor = useEditor({
  extensions: [
    StarterKit.configure({ codeBlock: false }),
    CodeBlockLowlight.configure({ lowlight }),
    SlashCommand.configure({
      suggestion: getSlashCommandSuggestion(),
    }),
  ],
});

Placeholder

Install @tiptap/extension-placeholder to show placeholder text:

pnpm add @tiptap/extension-placeholder
import Placeholder from "@tiptap/extension-placeholder";

const editor = useEditor({
  extensions: [
    StarterKit,
    Placeholder.configure({ placeholder: "Type / for commands..." }),
  ],
  content: "",
});

Styling

Both the editor UI and content area are styled using CSS variables from your shadcn theme. See the Styling guide for details on customization.

On this page