Letras Diferentes
Unicode research · Clipboard, Editors & Markup

contenteditable and Unicode Text: DOM Selection, IME Composition and Mutation Safety

Implement robust Unicode-aware contenteditable editors using beforeinput, DOM Range, Selection and composition events without splitting grapheme clusters.

Technical referenceUnicode and web platform interoperability

A contenteditable element is not a plain string input. The browser edits a DOM tree while the user's cursor refers to selection boundaries that may be text-node UTF-16 offsets. Unicode grapheme clusters and ongoing input-method composition can span multiple code points, so naive string slicing and DOM replacement are dangerous.

Understand editing hosts and DOM state

An editing host may contain text nodes, inline elements and nested formatting. Pressing Enter can create different DOM structures depending on browser and context; the editor must not assume that a newline always appears as U+000A in one text node.

To persist content, choose an internal document model rather than treating serialized innerHTML as the sole source of truth. Normalize DOM shape under deliberate transactions, preserve a mapping for selections, and avoid converting user input into HTML without sanitization.

Selection offsets use UTF-16 code units

A DOM Range boundary inside a Text node is specified as an offset in UTF-16 code units. For the string 'A😀B', the emoji occupies two units. Placing a boundary between the surrogate pair can be representable as a numeric offset but does not correspond to a valid user-perceived caret position.

JavaScript String.length and Text.length count code units. Iterating with for...of counts Unicode code points, not extended grapheme clusters. Use an explicit conversion layer when translating between DOM boundaries and grapheme-aware editor positions.

Input events are transaction signals, not all commands

An actual IME event trace can contain compositionstart, compositionupdate and compositionend, with provisional text and selection state changing throughout the session. The beforeinput event exposes an inputType describing an intended operation, such as insertText or a deletion type, when supported. Some events are cancelable and others are not, depending on the situation and browser. Code cannot assume every mutation can be intercepted and canceled.

When getTargetRanges() returns static ranges, use them according to the editing host and input event contract rather than assuming the current selection is the only affected region. Validate native behavior across browsers; account for undo/redo and paste as separate transaction classes.

Do not disrupt an active IME composition

East Asian input methods and other IMEs often build a temporary marked-text candidate before commit. Composition events can identify the start, updates and end of that interaction; InputEvent.isComposing indicates relevant state on input events. Exact event order varies by browser/platform.

If a reactive UI re-renders the full editing host on every input event, it can terminate composition, move the caret or duplicate content. Instead defer nonessential transformations, preserve selection and let the platform own the in-progress preedit. Test real Japanese, Chinese and Korean keyboards, not just programmatically dispatched events.

Grapheme-aware Backspace differs from code-unit deletion

The string é contains an ASCII base and combining acute accent; removing only the final code unit leaves the base, which may not match user expectations for Backspace. An emoji ZWJ sequence such as U+1F469 U+200D U+1F4BB combines multiple scalars into a frequently single displayed grapheme.

UAX #29 supplies default extended grapheme cluster boundaries, but real editing behavior may be tailored by locale or product. Intl.Segmenter can expose default boundaries where available. Always preserve an explicit policy for editing inside complex scripts and exceptions.

DOM normalization can silently invalidate saved selections

Merging adjacent text nodes with Node.normalize() or replacing innerHTML changes node identities and offsets. A saved Range tied to old nodes may collapse or be retargeted, making the caret jump after formatting.

Treat a contenteditable update as an atomic transaction: snapshot logical selection, apply a bounded DOM change, then map the selection to the new structure. Prefer targeted operations over replacing the entire subtree. Regression tests should include selection that spans nested elements.

Pasted markup needs an allowlist

Clipboard HTML may contain elements and attributes the editor does not support. Sanitize dangerous markup, normalize allowed inline styles into the editor's own schema, and use the plain-text fallback when rich paste cannot be handled safely.

Do not rely on character stripping as a sanitizer. Mathematical Unicode symbols, bidi isolates, ZWJ and combining marks can be valid text. Conversely, safe Unicode text does not justify injecting the surrounding HTML unescaped.

Direction and language metadata matter

A multilingual editor should preserve relevant lang and dir contexts on spans or blocks. The Unicode Bidirectional Algorithm determines visual ordering, while DOM selection positions and stored data remain tied to the logical sequence.

Hit testing mixed Arabic/Latin lines is more subtle than counting characters from left to right. Browser-native selection geometry and logical DOM offsets should be favored over a home-grown pixel-to-index formula.

Test the editor with actual user sequences

Automated key events do not fully simulate platform IMEs and composition. Use browser automation for basic typing, paste, undo/redo and selection, plus human or device-lab tests for real IME candidates and emoji selection.

Record DOM snapshots, input/composition events and serialized scalar sequences. Separate tests for semantic content correctness, selection correctness, paint and accessibility; one passing snapshot does not establish the editor is Unicode-safe.

Worked example: concrete source and observable output

This compact experiment isolates a defined data transformation. Use a standards-compliant parser or browser in an authorized test environment; a demonstration of raw HTML input is not an instruction to inject untrusted content into a live DOM. Treat displayed characters, HTML source spelling and serialized bytes as distinct measurements.

const host = document.querySelector('[contenteditable]');
host.addEventListener('beforeinput', e => {
  // e.inputType describes intent, not a guaranteed cancelable command.
  console.log(e.inputType, e.cancelable, e.isComposing);
});
const s = 'A👩\u200D💻e\u0301';
const seg = new Intl.Segmenter('en', {granularity: 'grapheme'});
console.log([...seg.segment(s)].map(x => x.segment));
// Map DOM Text-node UTF-16 offsets to these boundaries before mutation.

For a reproducible issue report, include the initial sequence of Unicode scalar values, whether Unicode normalization is expected, and the exact API boundary under test. If a result is different only in font appearance, record that separately from a change of text content.

Interoperability matrix and expected invariants

CaseFixtureInterpretation
DOM Text offsetUTF-16 code unitssurrogates can span two units
Intl.Segmentergraphemedefault clusters rather than glyph count
beforeinputinputType/cancelablenot every edit can be canceled
CompositionEventpreedit lifecycleavoid re-render during active composition
Selection/RangeDOM boundariesremap after mutations

Interpreting this matrix requires a declared contract: exact scalar identity is stronger than canonical equivalence, and canonical equivalence is different from visual similarity. An implementation may preserve text while intentionally transforming formatting or serialization syntax.

Validation workflow and failure classification

  1. DOM Text offset. Record the input specimen UTF-16 code units, define expected output, and compare the original Unicode code points to the observed representation. surrogates can span two units. If the display differs, inspect the parser, DOM, selection or rendering layer before rewriting the source text.
  2. Intl.Segmenter. Record the input specimen grapheme, define expected output, and compare the original Unicode code points to the observed representation. default clusters rather than glyph count. If the display differs, inspect the parser, DOM, selection or rendering layer before rewriting the source text.
  3. beforeinput. Record the input specimen inputType/cancelable, define expected output, and compare the original Unicode code points to the observed representation. not every edit can be canceled. If the display differs, inspect the parser, DOM, selection or rendering layer before rewriting the source text.
  4. CompositionEvent. Record the input specimen preedit lifecycle, define expected output, and compare the original Unicode code points to the observed representation. avoid re-render during active composition. If the display differs, inspect the parser, DOM, selection or rendering layer before rewriting the source text.
  5. Selection/Range. Record the input specimen DOM boundaries, define expected output, and compare the original Unicode code points to the observed representation. remap after mutations. If the display differs, inspect the parser, DOM, selection or rendering layer before rewriting the source text.

Once the failure layer has been identified, create a regression fixture with the actual source, expected data and browser or parser version. Test ordinary and adversarial strings, including UTF-16 surrogate pairs, decomposed combining accents, nonbreaking spaces and zero-width characters when relevant.

Common misdiagnoses

  • Understand editing hosts and DOM state: A common false conclusion is that all text representations share the same semantics. Preserve the original source, document the transformation rule, and compare the exact intermediate stage discussed above before choosing a fix.
  • Selection offsets use UTF-16 code units: A common false conclusion is that all text representations share the same semantics. Preserve the original source, document the transformation rule, and compare the exact intermediate stage discussed above before choosing a fix.
  • Input events are transaction signals, not all commands: A common false conclusion is that all text representations share the same semantics. Preserve the original source, document the transformation rule, and compare the exact intermediate stage discussed above before choosing a fix.
  • Do not disrupt an active IME composition: A common false conclusion is that all text representations share the same semantics. Preserve the original source, document the transformation rule, and compare the exact intermediate stage discussed above before choosing a fix.
  • Grapheme-aware Backspace differs from code-unit deletion: A common false conclusion is that all text representations share the same semantics. Preserve the original source, document the transformation rule, and compare the exact intermediate stage discussed above before choosing a fix.

Respect the distinction between valid Unicode data and hostile markup. A sanitizer makes decisions about executable HTML; a Unicode-aware editor makes decisions about text identity and caret boundaries. One does not replace the other.

Primary specifications and implementation references

These are the source specifications and API documents for the behaviors described. Version-dependent browser behavior should be verified against the actual implementation. The distinction between normative algorithms and recommended application design is intentional.

  • WHATWG HTML Editing. Reference for standards terminology or the corresponding browser and Unicode API; confirm the edition and feature support for the target environment.
  • W3C Input Events Level 2. Reference for standards terminology or the corresponding browser and Unicode API; confirm the edition and feature support for the target environment.
  • W3C Selection API. Reference for standards terminology or the corresponding browser and Unicode API; confirm the edition and feature support for the target environment.
  • DOM Standard Range. Reference for standards terminology or the corresponding browser and Unicode API; confirm the edition and feature support for the target environment.
  • Unicode Text Segmentation UAX #29. Reference for standards terminology or the corresponding browser and Unicode API; confirm the edition and feature support for the target environment.

Within the Clipboard, Editors & Markup virtual silo, this reference is part of the larger Unicode interoperability library. Additional published studies address neighboring data or editing layers: