Empty paragraph and span elements accumulate during editing
OS: Windows 11 · Device: Desktop or Laptop Any · Browser: Chrome 120.0 · Keyboard: US
Open case →Scenario
During editing operations, empty elements (empty paragraphs, divs, spans with no content) accumulate in the DOM. These elements can cause layout issues, make the HTML bloated, and create unexpected behavior. Browsers handle empty element cleanup inconsistently.
During editing operations, empty elements (empty paragraphs, divs, spans with no content) accumulate in the DOM. These elements can cause layout issues, make the HTML bloated, and create unexpected behavior. Browsers handle empty element cleanup inconsistently.
<p> or <div> elements<span> elements with style attributes may remain<li> elements may remainImplement cleanup logic:
function cleanupEmptyElements(element) {
// Remove empty spans (except those with meaningful attributes)
const spans = element.querySelectorAll('span');
spans.forEach(span => {
const hasContent = span.textContent.trim() || span.querySelector('img, br');
const hasOnlyWhitespace = span.textContent.trim() === '' && !span.querySelector('img, br');
if (hasOnlyWhitespace && !span.hasAttribute('data-keep')) {
// Move children to parent
const parent = span.parentNode;
while (span.firstChild) {
parent.insertBefore(span.firstChild, span);
}
span.remove();
}
});
// Remove empty paragraphs and divs (but keep at least one)
const blocks = element.querySelectorAll('p, div');
let hasContentBlock = false;
blocks.forEach(block => {
const hasContent = block.textContent.trim() || block.querySelector('img, br, ul, ol');
if (hasContent) {
hasContentBlock = true;
}
});
blocks.forEach(block => {
const hasContent = block.textContent.trim() || block.querySelector('img, br, ul, ol');
if (!hasContent && hasContentBlock) {
// Remove empty block, but keep structure if it's the only one
const parent = block.parentNode;
while (block.firstChild) {
parent.insertBefore(block.firstChild, block);
}
block.remove();
} else if (!hasContent && !hasContentBlock) {
// Keep at least one empty block with <br>
if (!block.querySelector('br')) {
block.appendChild(document.createElement('br'));
}
}
});
// Remove empty list items
const listItems = element.querySelectorAll('li');
listItems.forEach(li => {
if (!li.textContent.trim() && li.children.length === 0) {
const list = li.parentElement;
li.remove();
// Remove list if empty
if (list.children.length === 0) {
list.remove();
}
}
});
}
// Cleanup on input
element.addEventListener('input', () => {
requestAnimationFrame(() => {
cleanupEmptyElements(element);
});
});
Visual view of how this scenario connects to its concrete cases and environments. Nodes can be dragged and clicked.
Each row is a concrete case for this scenario, with a dedicated document and playground.
| Case | OS | Device | Browser | Keyboard | Status |
|---|---|---|---|---|---|
| ce-0111-empty-elements-accumulate | Windows 11 | Desktop or Laptop Any | Chrome 120.0 | US | draft |
| ce-0127-empty-paragraph-after-delete | Windows 11 | Desktop or Laptop Any | Chrome 120.0 | US | draft |
| ce-0140-empty-span-style-attributes | Windows 11 | Desktop or Laptop Any | Chrome 120.0 | US | draft |
| ce-0159-empty-elements-after-format-remove | Windows 11 | Desktop or Laptop Any | Safari 17.0 | US | draft |
| ce-0174-empty-div-after-list-removal | Windows 11 | Desktop or Laptop Any | Chrome 120.0 | US | draft |
This matrix shows which browser and OS combinations have documented cases for this scenario. Click on a cell to view the specific case.
| Browser | Windows |
|---|---|
| Chrome | |
| Safari |
Open a case to see the detailed description and its dedicated playground.
OS: Windows 11 · Device: Desktop or Laptop Any · Browser: Chrome 120.0 · Keyboard: US
Open case →OS: Windows 11 · Device: Desktop or Laptop Any · Browser: Chrome 120.0 · Keyboard: US
Open case →OS: Windows 11 · Device: Desktop or Laptop Any · Browser: Chrome 120.0 · Keyboard: US
Open case →OS: Windows 11 · Device: Desktop or Laptop Any · Browser: Safari 17.0 · Keyboard: US
Open case →OS: Windows 11 · Device: Desktop or Laptop Any · Browser: Chrome 120.0 · Keyboard: US
Open case →Other scenarios that share similar tags or category.
Editing text within blockquote elements in contenteditable behaves inconsistently across browsers. Pressing Enter, applying formatting, or pasting content may break the blockquote structure, create nested blockquotes, or behave unexpectedly.
After deleting an empty or inline element (e.g. span, b) inside contenteditable, typing causes the browser to recreate the deleted element, leading to unpredictable DOM and editor state.
When using document.execCommand('insertHTML', ...) to insert HTML content into a contenteditable region, the DOM structure may be broken or reformatted unexpectedly. Nested elements may be flattened or reorganized.
Applying multiple formatting operations (bold, italic, underline, etc.) creates nested HTML elements that can become complex and hard to manage. Browsers handle nested formatting differently, and the resulting DOM structure can be inconsistent.
Changing background color (highlighting) in contenteditable elements behaves inconsistently across browsers. Background colors may be applied as inline styles, may not persist when typing, or may interfere with text selection. The behavior differs from text color changes.
Have questions, suggestions, or want to share your experience? Join the discussion below.