Scenario

Empty elements accumulate in DOM during editing

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.

formatting
Scenario ID
scenario-empty-element-cleanup

Details

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.

Observed Behavior

Scenario 1: Empty paragraphs after deletion

  • Chrome/Edge: May leave empty <p> or <div> elements
  • Firefox: Similar behavior
  • Safari: May create different empty structures

Scenario 2: Empty spans after formatting removal

  • Chrome/Edge: Empty <span> elements with style attributes may remain
  • Firefox: Similar issues
  • Safari: Empty span handling varies

Scenario 3: Nested empty elements

  • Chrome/Edge: May create nested empty structures
  • Firefox: Similar behavior
  • Safari: Nested empty elements most common

Scenario 4: Empty list items

  • Chrome/Edge: Empty <li> elements may remain
  • Firefox: Similar behavior
  • Safari: Empty list handling inconsistent

Impact

  • Bloated HTML
  • Layout issues from empty elements
  • Unexpected spacing and behavior
  • Need for cleanup logic

Browser Comparison

  • Chrome/Edge: Generally better at cleanup but still leaves empty elements
  • Firefox: More likely to leave empty elements
  • Safari: Most likely to accumulate empty elements

Workaround

Implement 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);
  });
});

References

Scenario flow

Visual view of how this scenario connects to its concrete cases and environments. Nodes can be dragged and clicked.

React Flow mini map

Variants

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

Browser compatibility

This matrix shows which browser and OS combinations have documented cases for this scenario. Click on a cell to view the specific case.

Confirmed
Draft
No case documented

Cases

Open a case to see the detailed description and its dedicated playground.

Related Scenarios

Other scenarios that share similar tags or category.

Tags: structure

Blockquote editing behavior varies across browsers

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.

4 cases
Tags: dom

insertHTML breaks DOM structure and formatting

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.

1 case
Tags: structure

Nested formatting elements create complex DOM structures

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.

3 cases
Category: formatting

Background color changes behave inconsistently

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.

3 cases

Comments & Discussion

Have questions, suggestions, or want to share your experience? Join the discussion below.