The contenteditable Attribute
The contenteditable Attribute
Section titled “The contenteditable Attribute”The contenteditable attribute makes an element’s content editable by the user. It’s the foundation for building rich text editors, inline editing interfaces, and interactive content areas directly in the browser.
Syntax
Section titled “Syntax”<element contenteditable="value">Editable content</element>Values
Section titled “Values”| Value | Description |
|---|---|
true or "" (empty) | Content is editable |
false | Content is not editable (default) |
plaintext-only | Content is editable, but rich formatting is stripped |
Examples
Section titled “Examples”Basic Editable Content
Section titled “Basic Editable Content” Result
Rich Text Editing
Section titled “Rich Text Editing” Result
Plain Text Only Editing
Section titled “Plain Text Only Editing” Result
Inline Editing Pattern
Section titled “Inline Editing Pattern” Result
Nested Contenteditable
Section titled “Nested Contenteditable” Result
How It Works
Section titled “How It Works”Inheritance and Overriding
Section titled “Inheritance and Overriding”contenteditable is inherited by child elements but can be overridden:
<div contenteditable="true"> <p>This is editable</p> <p contenteditable="false">This is NOT editable</p> <p>This is editable again</p></div>Focus Behavior
Section titled “Focus Behavior”Editable elements can receive focus like form inputs:
const editor = document.querySelector('[contenteditable]');editor.focus();
// Get current cursor positionconst selection = window.getSelection();const range = selection.getRangeAt(0);Browser Default Behavior
Section titled “Browser Default Behavior”When content is editable:
- Enter key: Creates new paragraphs or
<div>elements (browser-dependent) - Formatting: Browsers insert
<b>,<i>,<u>tags when using shortcuts - Paste: Formatted content is preserved (unless
plaintext-only) - Undo/Redo: Browser provides basic undo functionality
Best Practices
Section titled “Best Practices”1. Provide Clear Visual Feedback
Section titled “1. Provide Clear Visual Feedback”Make it obvious when content is editable:
[contenteditable="true"] { border: 2px dashed #cbd5e1; padding: 10px; min-height: 100px;}
[contenteditable="true"]:hover { background: #f8fafc; cursor: text;}
[contenteditable="true"]:focus { outline: none; border-color: #3b82f6; background: white;}2. Use plaintext-only When Appropriate
Section titled “2. Use plaintext-only When Appropriate”For simple text inputs without formatting:
<!-- User comments, plain text fields --><div contenteditable="plaintext-only"> Enter your comment...</div>3. Sanitize User Input
Section titled “3. Sanitize User Input”Always sanitize content before saving or displaying:
function sanitizeContent(html) { const temp = document.createElement('div'); temp.textContent = html; // Strips all HTML tags return temp.innerHTML;}
// Or use a library like DOMPurifyconst clean = DOMPurify.sanitize(dirtyHTML);4. Handle Keyboard Events
Section titled “4. Handle Keyboard Events”Improve user experience with custom keyboard handling:
editor.addEventListener('keydown', (e) => { // Ctrl+S to save if (e.ctrlKey && e.key === 's') { e.preventDefault(); saveContent(); }
// Limit Enter key to line breaks instead of new paragraphs if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); document.execCommand('insertLineBreak'); }});5. Preserve Cursor Position
Section titled “5. Preserve Cursor Position”When updating content programmatically, preserve the cursor:
function saveSelection() { const selection = window.getSelection(); if (selection.rangeCount > 0) { return selection.getRangeAt(0); } return null;}
function restoreSelection(range) { if (range) { const selection = window.getSelection(); selection.removeAllRanges(); selection.addRange(range); }}6. Add aria-label for Accessibility
Section titled “6. Add aria-label for Accessibility”<div contenteditable="true" aria-label="Document editor" role="textbox" aria-multiline="true"> Content...</div>Common Use Cases
Section titled “Common Use Cases”1. WYSIWYG Editor
Section titled “1. WYSIWYG Editor”<div id="editor" contenteditable="true" style="min-height: 300px; border: 1px solid #ccc; padding: 15px;"> <h1>Document Title</h1> <p>Start typing...</p></div>2. Inline Editing
Section titled “2. Inline Editing”<h1 contenteditable="true" class="editable-title"> Click to edit title</h1>3. Code Editor
Section titled “3. Code Editor”<pre contenteditable="true" spellcheck="false"><code>function hello() { console.log('Hello, World!');}</code></pre>4. Comment Box
Section titled “4. Comment Box”<div contenteditable="plaintext-only" placeholder="Write a comment..." style="min-height: 100px;"></div>Accessibility Considerations
Section titled “Accessibility Considerations”ARIA Roles and Properties
Section titled “ARIA Roles and Properties”Make editable content accessible:
<div contenteditable="true" role="textbox" aria-label="Message content" aria-multiline="true" aria-required="true"></div>Keyboard Navigation
Section titled “Keyboard Navigation”Ensure all functionality is keyboard-accessible:
editor.addEventListener('keydown', (e) => { // Tab should move to next element, not insert tab character if (e.key === 'Tab') { e.preventDefault(); // Move focus to next element }});Screen Reader Compatibility
Section titled “Screen Reader Compatibility”<!-- Announce changes to screen readers --><div contenteditable="true" aria-live="polite" aria-atomic="true"></div>JavaScript API
Section titled “JavaScript API”Getting and Setting Content
Section titled “Getting and Setting Content”const editor = document.getElementById('editor');
// Get contentconst html = editor.innerHTML;const text = editor.textContent;
// Set contenteditor.innerHTML = '<p>New content</p>';editor.textContent = 'Plain text content';execCommand API
Section titled “execCommand API”// Format selected textdocument.execCommand('bold');document.execCommand('italic');document.execCommand('underline');
// Insert contentdocument.execCommand('insertHTML', false, '<strong>Bold text</strong>');document.execCommand('insertText', false, 'Plain text');
// Create listsdocument.execCommand('insertUnorderedList');document.execCommand('insertOrderedList');Selection API
Section titled “Selection API”const selection = window.getSelection();const selectedText = selection.toString();
// Get cursor positionif (selection.rangeCount > 0) { const range = selection.getRangeAt(0); const cursorPosition = range.startOffset;}Input Event Handling
Section titled “Input Event Handling”editor.addEventListener('input', (e) => { console.log('Content changed:', e.inputType); // Save to localStorage, trigger auto-save, etc.});
editor.addEventListener('beforeinput', (e) => { // Can prevent input if (shouldPreventInput(e)) { e.preventDefault(); }});Common Pitfalls
Section titled “Common Pitfalls”Browser Support
Section titled “Browser Support”Excellent support across all modern browsers:
| Feature | Chrome | Firefox | Safari | Edge |
|---|---|---|---|---|
true / false | All | All | All | All |
plaintext-only | 58+ | No | 10.1+ | 79+ |
Related Attributes
Section titled “Related Attributes”spellcheck- Enable/disable spell checking in editable contentautocapitalize- Control text capitalizationinputmode- Hint for virtual keyboard type
Related APIs
Section titled “Related APIs”- Selection API - Work with text selection and cursor position
- execCommand - Format and manipulate editable content (deprecated but still used)
- Input Events - Detect and respond to content changes