Summary
Raw `contentEditable` lets the browser own the DOM, producing cross-browser inconsistencies and dirty HTML that is hard to sanitize or serialize. Production editors — ProseMirror, Lexical, TipTap, Slate — own a typed document model and render from it, making serialization, validation, and collaborative editing tractable. Choose the framework by bundle size, schema flexibility, and collaborative-editing needs.
Jump to the interview angleDocument-model editor
A rich text editor built on contentEditable hands the browser control of the DOM. Every browser vendor implements editing commands differently — Chrome inserts <b>, Firefox may insert <strong>, and Safari diverges further. The result is arbitrary, inconsistent HTML that is unsafe to store or render without heavy sanitization.
Document-model editors (ProseMirror, Lexical, TipTap, Slate) invert this: they maintain a typed in-memory tree (nodes, marks, attributes), derive contentEditable DOM purely for display, and intercept every keystroke and selection event. The model is the source of truth; the DOM is a view. Serialization becomes a deterministic traversal of the model, not a parse of dirty HTML.
Never store or render raw contentEditable HTML
Any HTML collected from a contentEditable element is XSS-dangerous and browser-dependent. If you must accept raw HTML (e.g., paste from Word), run it through a server-side sanitizer (DOMPurify on the server, or a dedicated library like sanitize-html) with a strict allowlist. The safe default is to store the editor's own JSON model and derive HTML at render time from your own serializer.
Framework comparison: Lexical, ProseMirror/TipTap, Slate
| Concern | Lexical (Meta) | ProseMirror / TipTap | Slate | |
|---|---|---|---|---|
| Bundle size | ~22 kB min+gz core | ProseMirror ~50 kB; TipTap adds ~20 kB on top | ~100 kB with React peer | |
| Document model | Typed node classes; immutable state snapshots | Schema-validated node/mark graph; strict by design | Plain JSON AST; flexible but less constrained | |
| React integration | First-class; ships React binding from Meta | TipTap wraps ProseMirror with React/Vue adapter | Built for React; renders via virtual DOM | |
| Collaborative editing | Yjs binding via y-lexical (community) | Yjs via y-prosemirror (official); mature | Community Yjs plugin; less battle-tested | |
| Schema strictness | Enforced via node registration; prevents invalid trees | ProseMirror schema is strict; TipTap extensions add nodes | Ad-hoc; schema is your responsibility | |
| Maturity | Stable since 2022; powers Facebook composer | ProseMirror since 2015; production-grade | Stable since 2016; large ecosystem |
Selection, serialization, and collaborative editing
- Use the editor's Selection API, not `window.getSelection()` — models normalize cross-browser range quirks.
- Serialize to your own schema (JSON or Markdown), never to innerHTML; derive HTML from the model at read time.
- Collaborative editing via Yjs uses CRDTs: each client applies operations locally, and Yjs merges divergent histories without a central lock.
- y-prosemirror maps ProseMirror Steps to Yjs operations; both peers converge to the same document after any network partition.
- Presence (cursors, selections of other users) requires a separate awareness layer — Yjs provides `y-protocols/awareness` for this.
Which framework to pick
Choose Lexical for a React-first project where bundle size matters and you want first-party Meta support. Choose TipTap (over raw ProseMirror) when you need a batteries-included extension library and proven Yjs collaboration. Choose Slate when you need deep control over the data model and already have a custom JSON schema. Avoid raw contentEditable in all cases.
Interview angle
Interviewers probe whether you reach for contentEditable directly or know why a document model is mandatory. Show you can compare frameworks and explain serialization safety. Cover CRDTs for the collaborative variant.
Soundbite: "The editor owns the model; the DOM is just a view of it."
Key terms
- document model
- Typed in-memory tree the editor owns; DOM is derived from it, not the reverse.
- ProseMirror
- Low-level editor toolkit with a strict schema-validated node/mark graph; foundation of TipTap.
- Lexical
- Meta's extensible editor framework; typed node classes, immutable state, small core bundle.
- CRDT (Conflict-free Replicated Data Type)
- Data structure that merges concurrent edits from multiple clients without coordination.
- Yjs
- CRDT library for shared data types; y-prosemirror and y-lexical adapt it to editor operations.