You can make the contents of an HTML element editable, much like the text in a
form field, by adding the
contenteditable
attribute.
<div id="editable-div" contenteditable="true">Hello!</div>You can edit that div 👇
contenteditable is an enumerated attribute rather than a Boolean attribute. It
accepts these values:
trueor an empty value makes the element editablefalsemakes the element non-editableplaintext-onlyallows editing but disables rich-text formatting
If the attribute is missing or has an invalid value, its value is inherited from
the parent. With contenteditable="true", pasted formatting is retained; with
contenteditable="plaintext-only", the browser removes it.
Listening for changes on a contenteditable element
A contenteditable element has no value property, and the change event used
with form fields is not the right event here. Listen for the input event and
read the element's text instead:
const editableDiv = document.getElementById('editable-div')
if (editableDiv === null) {
throw new Error('Editable div not found')
}
editableDiv.addEventListener('input', event => {
console.log(event.currentTarget.innerText)
})Notice that getElementById() receives editable-div, without the # used in
a CSS selector. Use innerText when you want the rendered plain text. Use
innerHTML only when you intentionally need the element's markup.
The related
beforeinput event
fires before the browser changes the DOM. It is useful when an editor needs to
inspect or override an edit.
Older examples often use document.execCommand() for formatting and clipboard
operations. The method is deprecated and inconsistently implemented, so do not
build a new editor around it. For clipboard operations, use the Clipboard API.
A contenteditable React component
React warns when a contentEditable element also has children managed by React:

The warning is correct: Hello! is a child, even though it is only a text node.
The browser can modify that child while React assumes it owns the same DOM.
For a small plain-text editor, you can deliberately leave the editable DOM uncontrolled, mirror its text into state, and suppress the warning:
import { useState } from 'react'
function EditableDiv() {
const [divText, setDivText] = useState('Hello!')
return (
<>
<div
aria-label='Editable text'
aria-multiline='true'
className='pink-div'
contentEditable='plaintext-only'
onInput={event => setDivText(event.currentTarget.innerText)}
role='textbox'
suppressContentEditableWarning
>
Hello!
</div>
<div className='value-container'>
<strong>Value:</strong> {divText}
</div>
</>
)
}This example displays the current text elsewhere, but it does not render
divText back into the editable element. Replacing the element's children on
every keystroke can reset the caret or selection. A full rich-text editor also
has to manage selections, undo history, pasted content, and browser differences,
so a tested editor library is usually a better starting point.
Accessibility, forms, and security
Prefer an <input> or <textarea> when you only need a normal text field. They
provide built-in semantics, validation, and form submission. A contenteditable
element is focusable, but it is not a form control and its contents are not
submitted automatically.
If it acts as a text field, give it an accessible name and role="textbox". Add
aria-multiline="true" when Enter inserts a new line, as in the React example
above.
Finally, treat editable HTML as untrusted input. Rich content can contain markup
introduced through pasting or editing, so sanitize it before saving or rendering
it. plaintext-only is the safer choice when formatting is unnecessary, but it
does not replace server-side validation.
Conclusions
contenteditable is useful for inline renaming, editable demos, and as a
building block for rich-text editors. For ordinary plain-text input, native form
controls are simpler and more robust.