The essentials
Quick reference
One focused task per row. Jump to the related section for complete, working examples.
| Use | Syntax | Examples |
|---|---|---|
| Create a rich-text host | <div contenteditable="true" aria-label="Post body">
</div> | View examples |
| Create a plain-text host | <div
contenteditable="plaintext-only"
aria-label="Summary">
</div> | View examples |
| Opt out inside a host | <span contenteditable="false">Locked token</span> | View examples |
| Inspect effective state | editor.isContentEditable | View examples |
| Observe editing intent | editor.addEventListener('beforeinput', handleBeforeInput) | View examples |
| Read operation type | event.inputType === 'insertText' | View examples |
| React after mutation | editor.addEventListener('input', saveDraft) | View examples |
| Read plain text | const value = editor.textContent | View examples |
| Set trusted plain text | editor.replaceChildren(document.createTextNode(value)) | View examples |
| Focus a nested host | <div contenteditable="true" tabindex="0"></div> | View examples |
| Suggest a keyboard | <div
contenteditable="plaintext-only"
inputmode="numeric">
</div> | View examples |
| Request spellchecking | <div contenteditable="true" spellcheck="true"></div> | View examples |
| Render saved text safely | preview.textContent = savedValue | View examples |
The contenteditable attribute turns an element into an editing host, but it does not provide a document model, toolbar, sanitizer, history policy, or reliable serialization format. Treat browser editing as an input surface: choose rich or plain-text behavior deliberately, observe editing intent before mutation, preserve keyboard and focus conventions, and validate every persisted result at a trusted boundary.
Step by step
Detailed examples
Choose rich text, plain text, and inheritance deliberately
contenteditable is enumerated rather than Boolean: use the strings true, false, or plaintext-only. Missing or invalid values inherit from the parent, and HTMLElement.isContentEditable reports the effective result. Rich editing permits user-agent-generated markup; plaintext-only requests text editing and removes formatting when content is pasted, but support and exact editing behavior should still be tested in target browsers.
<label id="body-label">Post body</label>
<div id="body" contenteditable="true" aria-labelledby="body-label">
Draft <span contenteditable="false">#1042</span>
</div>
<label id="summary-label">Plain-text summary</label>
<div id="summary" contenteditable="plaintext-only" aria-labelledby="summary-label"></div>
<script>
console.log(body.isContentEditable); // true
</script> Separate cancellable intent from completed mutation
beforeinput describes the proposed edit through inputType and, for some operations, data or dataTransfer. Cancel only operations your editor model can replace correctly; composition, spellchecking, autofill, and some platform edits may be non-cancelable. The later input event signals that the DOM changed. Keyboard events alone are insufficient because editing can originate from paste, drag, speech, handwriting, or assistive technology.
<div id="headline" contenteditable="plaintext-only" aria-label="Headline"></div>
<p id="count"></p>
<script>
const limit = 80;
headline.addEventListener('beforeinput', (event) => {
if (!event.cancelable || !event.inputType.startsWith('insert')) return;
const incoming = event.data ?? '';
if (headline.textContent.length + incoming.length > limit) event.preventDefault();
});
headline.addEventListener('input', () => {
count.textContent = `${headline.textContent.length}/${limit}`;
});
</script> Make the application data model explicit
The DOM inside a rich editing host is browser-managed presentation, not a stable cross-browser interchange format. Use textContent when the product stores text. For structured editors, translate permitted DOM shapes into a defined model and render that model back through DOM construction. Setting text with a Text node avoids HTML parsing and preserves literal characters such as angle brackets.
<div id="note" contenteditable="plaintext-only" aria-label="Note"></div>
<button id="restore" type="button">Restore</button>
<script>
let draft = '';
note.addEventListener('input', () => { draft = note.textContent; });
restore.addEventListener('click', () => {
note.replaceChildren(document.createTextNode(draft));
note.focus();
});
</script> Preserve focus, composition, and virtual-keyboard behavior
An editing host participates in focus navigation, but nested hosts are not automatically added to the tab sequence; add tabindex=0 only when the nested editor is independently operable. inputmode is a keyboard hint, not validation. Avoid intercepting ordinary editing shortcuts, and do not save partial input during an active IME composition unless the product explicitly supports that workflow.
<div id="amount" contenteditable="plaintext-only" inputmode="decimal" aria-label="Amount"></div>
<p id="message" role="status"></p>
<script>
let composing = false;
amount.addEventListener('compositionstart', () => { composing = true; });
amount.addEventListener('compositionend', validate);
amount.addEventListener('input', () => { if (!composing) validate(); });
function validate(event) {
composing = false;
message.textContent = /^\d+(?:[.,]\d+)?$/.test(amount.textContent) ? '' : 'Enter a number.';
}
</script> Treat editable HTML as untrusted input
Users can introduce markup through paste, drag and drop, DOM tools, restored drafts, and synchronization—not only through typing. Never trust content because it originated in a contenteditable element. Prefer plain text; if rich content is required, enforce an allowlist with a maintained sanitizer at ingestion and again on the server, normalize URLs, and apply a restrictive Content Security Policy as defense in depth.
<div id="editor" contenteditable="plaintext-only" aria-label="Message">Hello</div>
<output id="preview"></output>
<script>
editor.addEventListener('input', () => {
const savedValue = editor.textContent;
preview.textContent = savedValue;
});
</script> Local code tester
Inspect editing intent and safe text output
Edit the note to see beforeinput operations and a text-only preview without treating user content as markup.
Sources and further reading
References
Authoritative documentation used to verify and expand this cheat sheet.
Help us improve
Found a typo or missing example?
Tell us what would make this cheat sheet clearer, more complete, or more useful.



