Frontend Development 12 min read

Building a Markdown Rich Text Editor with ProseMirror and Remirror in React

This tutorial walks through creating a lightweight rich‑text editor using ProseMirror's core modules, enhancing it with plugins like history and input rules, and then simplifying development with the Remirror React library to support Markdown formatting and custom extensions.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Building a Markdown Rich Text Editor with ProseMirror and Remirror in React

In the previous article we set up an Electron development environment that loads a web page; now we add the main feature—Markdown editing—by leveraging ProseMirror to build a rich‑text editor.

ProseMirror Core Modules

ProseMirror consists of several essential packages:

prosemirror-model defines the document model describing editor content.

prosemirror-state provides a single data structure representing the full editor state, including selections and a transaction system for state updates.

prosemirror-view renders a given state as an editable DOM element and handles user interaction.

prosemirror-transform offers undo/redo‑able document modifications, building on the transaction functionality of prosemirror-state .

Additional frequently used modules include prosemirror-collab and prosemirror-gapcursor .

Simple Editor Example

import { EditorState } from "prosemirror-state");
import { EditorView } from "prosemirror-view";
import { Schema, DOMParser } from "prosemirror-model";
import { schema } from "prosemirror-schema-basic";
import { useEffect, useRef } from "react";
import React from "react";

export const Editor = () => {
  const schemaIntance = new Schema({
    nodes: schema.spec.nodes,
    marks: schema.spec.marks,
  });

  const view = useRef
();

  useEffect(() => {
    view.current = new EditorView(document.getElementById("editor"), {
      state: EditorState.create({
        doc: DOMParser.fromSchema(schemaIntance).parse(document.getElementById("content")!),
        schema: schemaIntance,
      }),
    });
  }, []);

  return (
Hello ProseMirror
);
};

The editor is minimal and lacks common features such as handling the Enter key for new lines.

Enhancing with Plugins

We can add functionality using ProseMirror plugins like prosemirror-history (undo/redo), prosemirror-keymap (key bindings), and prosemirror-commands (basic editing commands).

import { undo, redo, history } from "prosemirror-history";
import { keymap } from "prosemirror-keymap";
import { baseKeymap } from "prosemirror-commands";

useEffect(() => {
  view.current = new EditorView(document.getElementById("editor"), {
    state: EditorState.create({
      doc: DOMParser.fromSchema(schemaIntance).parse(document.getElementById("content")!),
      schema: schemaIntance,
      plugins: [
        history(),
        keymap({ "Mod-z": undo, "Mod-y": redo }),
        keymap(baseKeymap),
      ],
    }),
  });
}, []);

Now the editor supports undo and redo operations via keyboard shortcuts.

Input Rules for Markdown‑Style Formatting

The prosemirror-inputrules module lets us define patterns that automatically transform typed text. For example, typing hello (surrounded by double asterisks) can be turned into a bold node.

import { inputRules, InputRule } from "prosemirror-inputrules";

view.current = new EditorView(document.getElementById("editor"), {
  state: EditorState.create({
    doc: DOMParser.fromSchema(schemaIntance).parse(document.getElementById("content")!),
    schema: schemaIntance,
    plugins: [
      inputRules({
        rules: [
          new InputRule(/\*\*[^*]{1,}\*\*$/,
            (state, match, from, to) => {
              const mark = state.schema.mark("strong");
              const str = match[0].substring(2, match[0].length - 2);
              const node = state.schema.text(str, [mark]);
              return state.tr.replaceWith(from, to, node);
            })
        ],
      }),
    ],
  }),
});

Combined with history and keymap , the editor can undo the automatic transformation using CommandOrCtrl+z .

Remirror: A React Wrapper for ProseMirror

Remirror builds on ProseMirror to provide a React‑friendly API for constructing cross‑platform rich‑text editors. It is not a drop‑in solution like Draft.js but offers extensibility through extensions.

Installation

npm add remirror @remirror/react @remirror/pm

Creating a Manager

Before rendering a Remirror editor, we create a manager that controls behavior. Extensions such as MarkdownExtension add markdown parsing support.

import React from "react";
import { MarkdownExtension } from "remirror/extensions";
import { Remirror, useRemirror } from "@remirror/react";
import "remirror/styles/all.css";

const Editor = () => {
  const { manager, state } = useRemirror({
    extensions: () => [new MarkdownExtension()],
    content: "",
    selection: "start",
    stringHandler: "markdown",
  });

  return (
);
};

export default function App() { return
; }

Adding Extensions

We can add built‑in extensions like BoldExtension to enable markdown‑style bold syntax.

import { MarkdownExtension, BoldExtension } from "remirror/extensions";

const { manager, state } = useRemirror({
  extensions: () => [new MarkdownExtension(), new BoldExtension()],
  content: "",
  selection: "start",
  stringHandler: "markdown",
});

Typing **bold** automatically renders as bold text.

Custom Extension Example

The following shows a simplified implementation of a custom BoldExtension using Remirror's core decorators.

export interface BoldOptions { weight?: Static
; }

@extension
({
  defaultOptions: { weight: undefined },
  staticKeys: ['weight'],
})
export class BoldExtension extends MarkExtension
{
  get name() { return 'bold' as const; }
  createTags() { return [ExtensionTag.FormattingMark, ExtensionTag.FontStyle]; }
  // ... (schema, input rules, commands, key bindings) ...
}

The custom extension mirrors the functionality we achieved directly with ProseMirror modules.

Rendering the Editor and Toolbar

import { BoldExtension, HeadingExtension, ItalicExtension, MarkdownExtension, PlaceholderExtension } from "remirror/extensions";

const Menu = () =>
alert("button b")}>B
;

export const Markdown = () => {
  const { manager, state } = useRemirror({
    extensions: () => [
      new PlaceholderExtension({ placeholder: "请输入文字" }),
      new BoldExtension(),
      new ItalicExtension(),
      new MarkdownExtension(),
      new HeadingExtension(),
    ],
    content: "",
    selection: "start",
    stringHandler: "markdown",
  });

  return (
);
};

When custom menus are needed, the EditorComponent can be used inside Remirror to render the editor while the developer supplies their own toolbar.

Conclusion

By following the patterns shown for ProseMirror plugins and Remirror extensions, developers can quickly assemble a markdown‑capable rich‑text editor in a React/Electron environment and extend it further according to project requirements.

Frontend DevelopmentreactRich Text EditorMarkdownProseMirrorRemirror
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.