insertHTML

How insertHTML inputType inserts HTML content and varies across browsers.

Overview

The insertHTML inputType is triggered when HTML content is inserted programmatically (e.g., from paste operations or programmatic insertion). The browser parses the HTML string and inserts it as DOM nodes at the selection position.

Basic Behavior

Scenario 1: HTML string inserted at cursor

Before (Cursor position)

HTML:
<p>Hello|world</p>

After insertHTML('<strong>bold</strong>')

HTML:
<p>Hello<strong>bold</strong>world</p>
HTML parsed and inserted as DOM nodes

Scenario 2: HTML replaces selected text

Before (Text selected)

HTML:
<p>Hello world text</p>

After insertHTML('<em>italic</em>')

HTML:
<p>Hello <em>italic</em> text</p>
Selected text replaced with parsed HTML

Security Considerations

⚠️ XSS Risk

Warning: Inserting arbitrary HTML can lead to XSS (Cross-Site Scripting) vulnerabilities. Always sanitize HTML content before insertion.

Use libraries like DOMPurify to sanitize HTML before inserting it into contenteditable elements.

Browser-Specific Behavior

  • HTML parsing behavior may vary between browsers
  • Some browsers may strip or modify certain HTML elements for security reasons
  • Invalid HTML may be auto-corrected or ignored depending on the browser

IME Composition + insertHTML

⚠️ Critical Issue

During IME composition, inserting HTML (e.g., from paste operations) may cancel the active composition. The composition text may be lost or the HTML may be inserted in an unexpected location.

When handling paste operations, check for active composition state before inserting HTML to avoid interrupting the user's input.

Editor-Specific Handling

Different editor frameworks handle HTML insertion differently, typically converting HTML to their internal model format. Here's how major editors implement insertHTML:

Slate.js

HTML Insertion

Slate parses HTML and converts it to Slate nodes:

    import { Editor, Transforms } from 'slate';
import { jsx } from 'slate-hyperscript';

element.addEventListener('beforeinput', (e) => {
  if (e.inputType === 'insertHTML' && e.dataTransfer) {
    e.preventDefault();
    
    // Parse HTML string to Slate nodes
    const htmlString = e.dataTransfer.getData('text/html');
    const fragment = new DOMParser().parseFromString(htmlString, 'text/html');
    
    // Convert DOM nodes to Slate nodes (using slate-hyperscript or custom parser)
    const nodes = Array.from(fragment.body.childNodes).map(node => {
      // Convert DOM node to Slate node structure
      // This is a simplified example - real implementation would be more complex
      return jsx('element', { type: 'paragraph' }, [{ text: node.textContent }]);
    });
    
    // Insert nodes at selection
    Transforms.insertNodes(editor, nodes);
  }
});
  
  • HTML parsing: Parses HTML string into DOM, then converts to Slate nodes.
  • Model conversion: Converts browser HTML structure to Slate's internal node model.
  • Schema enforcement: Ensures inserted nodes conform to Slate schema.
ProseMirror

HTML Insertion

ProseMirror uses DOMParser to parse HTML into ProseMirror nodes:

    import { DOMParser } from 'prosemirror-model';
import { schema } from './schema';

view.dom.addEventListener('beforeinput', (e) => {
  if (e.inputType === 'insertHTML' && e.dataTransfer) {
    e.preventDefault();
    const { state, dispatch } = view;
    
    const htmlString = e.dataTransfer.getData('text/html');
    const dom = new DOMParser(window).parseFromString(htmlString, 'text/html');
    
    // Parse HTML into ProseMirror nodes using schema
    const fragment = DOMParser.fromSchema(schema).parse(dom.body);
    
    // Insert fragment at selection
    const tr = state.tr.replaceSelection(fragment);
    dispatch(tr);
  }
});
  
  • DOMParser: Uses ProseMirror's DOMParser.fromSchema() to parse HTML.
  • Schema-aware: HTML is parsed according to ProseMirror schema rules.
  • Transaction-based: Creates a transaction for undo/redo support.
Draft.js

HTML Insertion

Draft.js uses convertFromHTML to convert HTML to ContentState:

    import { EditorState, ContentState } from 'draft-js';
import { stateFromHTML } from 'draft-js-import-html';

element.addEventListener('beforeinput', (e) => {
  if (e.inputType === 'insertHTML' && e.dataTransfer) {
    e.preventDefault();
    
    const htmlString = e.dataTransfer.getData('text/html');
    
    // Convert HTML to ContentState
    const contentState = stateFromHTML(htmlString);
    
    // Get current selection
    const selection = editorState.getSelection();
    
    // Insert content at selection
    const newContentState = Modifier.replaceWithFragment(
      editorState.getCurrentContent(),
      selection,
      contentState.getBlockMap()
    );
    
    const newState = EditorState.push(
      editorState,
      newContentState,
      'insert-fragment'
    );
    
    setEditorState(newState);
  }
});
  
  • HTML import: Uses stateFromHTML to convert HTML to ContentState.
  • Block conversion: Converts HTML elements to Draft.js ContentBlocks.
  • Modifier API: Uses Modifier.replaceWithFragment to insert content.

Related resources