How to Use contenteditable Without Losing Control
Define an editing model, preserve selection, normalize pasted markup, and separate transient DOM state from saved content.
The short version
contenteditable gives you browser editing behavior, not a complete editor architecture.
Choose the unit of editing
Making an entire application surface editable creates ambiguous boundaries. A region-based model gives each paragraph, heading, list item, or table cell an identity that can be saved, commented on, and synchronized independently.
Keep controls, drag handles, and annotations outside the editable subtree when possible. That prevents the browser from treating interface chrome as document content.
Treat paste as an import
Clipboard HTML often carries inline styles, office-specific attributes, nested spans, and unsupported elements. Parse it, keep the structures your editor supports, and normalize the rest to a predictable schema.
<p
contenteditable="true"
data-region-key="region-intro"
aria-label="Editable introduction"
>
Start writing…
</p>Separate local editing from remote updates
The browser selection belongs to the live DOM. Your persisted model belongs to the application. Treating those as separate layers prevents many cursor jumps and lost-edit bugs.
- Do not replace the active DOM node on every keystroke.
- Preserve the selection when applying safe remote patches.
- Debounce persistence, but make sync state visible.
- Resolve conflicts at the smallest stable region.
- Store document versions before destructive transformations.