The React rich text editor with a component and a hook.
Drop in the controlled <RichTextEditor> component for a full toolbar in one line, or build your own UI Tiptap-style with the useRichTextEditor hook. Same engine either way: AI Toolkit, per-node CRDT collaboration, tracked changes, and clean HTML output.
Built to respect React’s ownership model.
value/onChange component, or a headless hook whose state re-renders your own buttons on every selection change.RichTextEditorReactHandle exposes getHTMLCode, getJSON/setJSON, focus, statistics, outline, and the plugin surfaces.editor.destroy(), so React 18 StrictMode double-invoke and App Router client components just work.Install once, pick your integration style
One npm package ships the engine, the React bindings, and the plugin bundle. React 18+ is a peer dependency.
npm install @richscripts/richtexteditor
Option 1 — the drop-in component
A controlled component with the familiar value/onChange contract. Pass any editor option through config — toolbar presets, height, upload endpoints, AI settings.
import { useState } from "react";
import { RichTextEditor } from "@richscripts/richtexteditor/react";
export function Composer() {
const [html, setHtml] = useState("<p>Draft your update...</p>");
return <RichTextEditor value={html} onChange={setHtml} config={{ height: 400 }} />;
}Option 2 — bring your own UI with the hook
useRichTextEditor returns the live editor plus a state snapshot (bold, h2, canUndo, ...) that re-renders your buttons as the selection moves — the pattern Tiptap and Lexical users expect, without giving up the built-in plugins. Use onMouseDown + preventDefaultso your buttons don’t steal focus from the editor.
import { useRichTextEditor, EditorContent } from "@richscripts/richtexteditor/react";
export function Editor() {
const editor = useRichTextEditor({ value: "<p>Hello</p>" });
return (
<>
<button
onMouseDown={(e) => { e.preventDefault(); editor.run("bold"); }}
className={editor.state.bold ? "is-active" : ""}
>Bold</button>
<button disabled={!editor.state.canUndo}
onMouseDown={(e) => { e.preventDefault(); editor.run("undo"); }}
>Undo</button>
<EditorContent editor={editor} />
</>
);
}Reading and writing content imperatively
Attach a ref to get a typed RichTextEditorReactHandle: HTML in/out, the structured JSON document model, text statistics, document outline, and accessors for AI Toolkit, comments, tracked changes, and collaboration.
import { useRef } from "react";
import { RichTextEditor } from "@richscripts/richtexteditor/react";
import type { RichTextEditorReactHandle } from "@richscripts/richtexteditor/react";
export function Form() {
const editorRef = useRef<RichTextEditorReactHandle>(null);
const submit = () => {
const html = editorRef.current?.getHTMLCode() ?? "";
const json = editorRef.current?.getJSON(); // structured document
// send html / json to your API ...
};
return (
<>
<RichTextEditor ref={editorRef} defaultValue="<p></p>" />
<button onClick={submit}>Save</button>
</>
);
}Next.js and server rendering
The editor is a client-side surface. In the App Router, wrap it in a "use client" component; this very site is a Next.js app running the editor on its demo pages. For rendering saved content on the server, the package’s server entry converts the structured JSON to static HTML without a DOM.
"use client";
// The editor renders into a live DOM, so in Next.js App Router keep it in a
// client component. Server components can pass initial HTML down as a prop.
import { RichTextEditor } from "@richscripts/richtexteditor/react";
export default function EditorClient({ initialHtml }: { initialHtml: string }) {
return <RichTextEditor defaultValue={initialHtml} />;
}getJSON/setJSON.React starter downloadA complete Vite + React sample app is in the download bundle.Why teams pick it over building on a framework-only editor
- Everything is included: AI Toolkit (bring your own key), per-node Yjs CRDT collaboration, tracked changes, comments, and revision history are part of the license — not premium add-ons.
- Clean HTML out: the output is publish-ready markup, plus a structured JSON model when you want validation or server rendering.
- Perpetual license from $129: no per-seat AI pricing and no subscription; see pricing and the Tiptap / Lexical comparisons.