Reusable React + TypeScript rich text editor, powered by Tiptap/ProseMirror, KaTeX, and DOMPurify. Includes an interactive local playground and an installable ESM package with TypeScript declarations.
Release: 0.1.0. This is an initial library release, not full parity with every RichTextEditor.com demo, and not an independently security-audited product.
Requires Node.js 22.12+ and npm.
npm ci
npm run devOpen the local address printed by Vite. The demo is in-memory: changes disappear on reload or when switching examples. It makes no persistence or AI requests. External images, if inserted, contact their hosts.
# In this library directory:
npm run build
npm pack
# In your React app (replace the path):
npm install @codeverta/rich"use client"; // For a Next.js client component
import { useRef } from "react";
import {
RichTextEditor,
type RichTextEditorHandle,
type EditorValue,
} from "@codeverta/rich";
import "@codeverta/rich/style.css";
import "katex/dist/katex.min.css";
export function DocumentEditor() {
const editor = useRef<RichTextEditorHandle>(null);
function handleChange(value: EditorValue) {
// value.html: sanitized HTML
// value.json: editor document (validate again at the server)
// value.text: plain text
console.log(value.text.length);
}
return (
<RichTextEditor
ref={editor}
initialHTML="<h1>My document</h1><p>Start writing…</p>"
onChange={handleChange}
onError={(error) => console.error(error.message)}
label="Article editor"
/>
);
}React and React DOM are peer dependencies; tested here with React 19. React 18 compatibility is declared but has not been separately tested. Import both CSS files. No global CSS reset is included in the library. The demo CSS is separate. In server-rendered frameworks, use a client component; pure conversion utilities require a browser DOM. The editor defers initialization until the client.
| Capability | Status |
|---|---|
| Paragraphs; headings 1–6; bold, italic, underline, strike | Implemented |
| Font family, size, color, highlight | Implemented |
| Subscript, superscript, inline code, clear formatting | Implemented |
| Left, center, right, justified alignment | Implemented |
| Ordered, unordered, nested task lists | Implemented; list nesting uses standard editor keyboard shortcuts |
| Blockquotes, code blocks, dividers, hard breaks | Implemented |
| Undo/redo and keyboard shortcuts | Implemented |
| Table insertion, column resizing, add/delete rows/columns | Implemented |
| Merge/split cells, toggle header, delete table | Implemented; select adjacent cells before merge |
| Links, autolinks, unlink | Implemented; explicit permitted URL schemes |
| Images | HTTPS URL insertion and alternative text; optional upload callback for PNG, JPEG, GIF, and WebP files from picker, paste, or drop |
| Math | Inline/block LaTeX; KaTeX preview; presets; click equation to edit |
| HTML source editor, paste sanitization | Implemented |
| HTML, JSON, plain-text import/export | Implemented; JSON schema is specific to this version |
| Markdown import/export | Basic, lossy conversion; styling, task state and table structure may be lost; math source exported as dollar-delimited LaTeX, imported as text |
| Find/replace | Case-insensitive literal matching across inline formatting within each block; no regex or cross-block matching |
| Word/character count, fullscreen, read-only | Implemented |
| Multiple editor instances | Implemented, isolated state |
| Print/PDF | Browser print dialog; host app controls final pagination |
| Image storage, asset manager, video/audio embeds | Host app must provide an image upload endpoint and storage; asset manager and video/audio embeds are not implemented |
| Real-time collaboration, presence, comments, tracked changes, version history | Not implemented |
| AI, translation, voice dictation, mentions | Not implemented |
| Word/Excel import fidelity, DOCX export, email-safe HTML | Not implemented |
| Paginated layout, templates, advanced image editing | Not implemented |
| Accessibility certification, mobile/IME/browser compatibility matrix | Not audited; native buttons, labels, dialogs, and editor keyboard behavior provided |
| Prop | Type | Behavior |
|---|---|---|
initialHTML |
string |
Sanitized initial content. Changing this prop does not replace the document; use the ref or remount. |
onChange |
(value: EditorValue) => void |
Called on document edits; receives HTML, JSON, and text. Debounce persistence in the host app. |
onError |
(error: Error) => void |
Reports UI import/paste/command failures. Invalid initial HTML can throw during initialization; use an error boundary. |
onImageUpload |
(file: File) => Promise<string> |
Optional. Upload one validated PNG/JPEG/GIF/WebP file and return its full HTTPS URL. Enables picker, paste, and drop uploads. |
readOnly |
boolean |
Disables document modification through the component and its ref. |
placeholder |
string |
Initial placeholder configuration. |
label |
string |
Accessible editor label. |
className |
string |
Class on the editor root. |
Ref methods: getValue(): EditorValue | null, setHTML(html): void, setJSON(json: unknown): void, focus(): void, clear(): void. Getters return null before initialization. Setters may throw for invalid input or read-only mode. Handle these errors in the host app. No raw editor instance is exposed, to keep the public input boundary explicit.
Provide your application's authenticated upload endpoint through onImageUpload. The editor checks declared type, extension, file signature, and a 5 MB size ceiling before calling it. A successful callback must return a full HTTPS image URL; rejected promises and unsafe URLs appear in onError and the editor UI. Without the callback, file paste/drop is rejected and image insertion by HTTPS URL remains available.
async function uploadImage(file: File): Promise<string> {
const body = new FormData();
body.append("image", file);
const response = await fetch("/api/images", {
method: "POST",
body,
credentials: "same-origin",
});
if (!response.ok) throw new Error("Image upload failed.");
const result: unknown = await response.json();
if (
!result ||
typeof result !== "object" ||
!("url" in result) ||
typeof result.url !== "string"
)
throw new Error("Image upload returned an invalid response.");
return result.url;
}
<RichTextEditor onImageUpload={uploadImage} onError={console.error} />;The host endpoint must authenticate and authorize the upload, check the decoded content and dimensions, re-encode or otherwise safely process images, enforce storage limits, and return a URL served over HTTPS. Client checks can be bypassed.
Exports: sanitizeHTML, safeURL, jsonToHTML, markdownToHTML, htmlToMarkdown, MAX_HTML_LENGTH, MAX_LATEX_LENGTH, and the component/types.
See SECURITY.md for the complete trust boundary. HTML export is an editor fragment, not a complete web page. Math HTML stores LaTeX source in data-latex; reopening it through this editor renders equations. Render exported fragments in another product only after your own output sanitization. JSON is an interchange format, not a reason to trust stored content.
HTML/text file input is limited to 1 MB; direct HTML input to 1,000,000 JavaScript characters. Editor typing is limited to 200,000 characters, and equation dialog input to 4,096 characters. JSON is additionally limited to 20,000 nodes/marks and depth 64. These bounds reduce accidental resource exhaustion; they do not guarantee CPU/memory limits for every adversarial document. Imported documents over the typing character limit may open but need shortening before further insertion.
npm run check # regression tests, TypeScript, library and demo builds
npm audit # known dependency advisories at the time of execution
npm pack # installable packageTests exercise malicious HTML and URLs, CSS filtering, DOM clobbering, JSON limits, math trust, formatting, undo/redo, tables, read-only mode, independent instances, equation/image dialogs, and image upload validation and failures. Tests run in jsdom, not a full browser engine. No penetration test or cross-browser audit is implied.
Source is under src/; demo under demo/; tests under tests/. Library build output is dist/, demo build output is demo-dist/. The package does not contain a backend and is not published to npm.
- Feature reference: https://richtexteditor.com/demos
- Tiptap: https://tiptap.dev/docs/editor/getting-started/overview
- Mathematics extension: https://tiptap.dev/docs/editor/extensions/nodes/mathematics
- DOMPurify security model: https://github.com/cure53/DOMPurify/wiki/Security-Goals-%26-Threat-Model
The supplied code is MIT licensed. Third-party dependencies retain their own licenses.