Scenario

beforeinput and input events have different inputType values

During IME composition or in certain browser/IME combinations, the beforeinput event may have a different inputType than the corresponding input event. For example, beforeinput may fire with insertCompositionText while input fires with deleteContentBackward. This mismatch can cause handlers to misinterpret the actual DOM change and requires storing beforeinput's targetRanges for use in input event handling.

ime
Scenario ID
scenario-beforeinput-input-inputtype-mismatch

Details

During IME composition or in certain browser/IME combinations, the beforeinput event may have a different inputType than the corresponding input event. This mismatch can cause handlers to misinterpret the actual DOM change and requires storing beforeinput’s targetRanges for use in input event handling.

Observed Behavior

When composing text with an IME, the following mismatch can occur:

  1. beforeinput event fires with inputType: 'insertCompositionText'

    • e.isComposing === true
    • e.getTargetRanges() returns ranges indicating where composition text will be inserted
    • e.data contains the composition text
  2. input event fires with inputType: 'deleteContentBackward' (or other different type)

    • The actual DOM change may be a deletion rather than insertion
    • The inputType doesn’t match what was indicated in beforeinput
    • e.data may be null or different from beforeinput.data

Impact

  • Misinterpretation of DOM changes: Handlers that rely on inputType to determine what happened will get incorrect information
  • Lost context: The targetRanges from beforeinput are crucial for understanding what actually changed, but they’re not available in input events
  • Incorrect undo/redo: Undo/redo stacks may record the wrong operation type
  • State synchronization issues: Application state may become inconsistent with DOM state
  • Event handler logic errors: Handlers expecting matching inputType values will fail

Browser Comparison

  • Chrome/Edge: Generally consistent inputType between beforeinput and input during composition
  • Firefox: May have mismatches in certain IME scenarios
  • Safari: More likely to have inputType mismatches, especially on iOS
  • Mobile browsers: Higher likelihood of mismatches due to text prediction and IME variations

Workaround

Store targetRanges from beforeinput events and use them in input event handlers:

let lastBeforeInputTargetRanges = null;
let lastBeforeInputType = null;
let lastBeforeInputData = null;

element.addEventListener('beforeinput', (e) => {
  // Store targetRanges, inputType, and data for use in input handler
  lastBeforeInputTargetRanges = e.getTargetRanges?.() || [];
  lastBeforeInputType = e.inputType;
  lastBeforeInputData = e.data;
  
  // Handle beforeinput normally
  if (e.inputType === 'insertCompositionText') {
    // Prepare for composition text insertion
  }
});

element.addEventListener('input', (e) => {
  // Check for inputType mismatch
  if (lastBeforeInputType && e.inputType !== lastBeforeInputType) {
    console.warn('inputType mismatch:', {
      beforeinput: lastBeforeInputType,
      input: e.inputType,
      beforeinputData: lastBeforeInputData,
      inputData: e.data
    });
    
    // Use targetRanges from beforeinput to understand actual change
    if (lastBeforeInputTargetRanges && lastBeforeInputTargetRanges.length > 0) {
      // The targetRanges indicate what was actually changed
      // Process based on targetRanges rather than inputType
      handleActualChange(lastBeforeInputTargetRanges, e);
    }
  } else {
    // Normal case: inputType matches
    handleInput(e);
  }
  
  // Clear stored values after processing
  lastBeforeInputTargetRanges = null;
  lastBeforeInputType = null;
  lastBeforeInputData = null;
});

function handleActualChange(targetRanges, inputEvent) {
  // Reconstruct what actually happened using targetRanges
  for (const range of targetRanges) {
    // Convert StaticRange to Range for inspection
    const actualRange = document.createRange();
    actualRange.setStart(range.startContainer, range.startOffset);
    actualRange.setEnd(range.endContainer, range.endOffset);
    
    // Check what's in the range now vs what was there before
    // This tells you the actual change regardless of inputType
  }
}

Important Notes:

  • targetRanges are only available in beforeinput events, not in input events
  • targetRanges are StaticRange objects that may become invalid after DOM changes
  • Always check if ranges are still valid before using them
  • Consider comparing DOM state before and after to understand actual changes
  • Don’t rely solely on inputType - always verify with DOM inspection

Best Practices

  1. Always store targetRanges: Save targetRanges from beforeinput for use in input handlers
  2. Compare inputType values: Check if beforeinput.inputType matches input.inputType
  3. Inspect DOM directly: When mismatch occurs, inspect DOM state to understand actual change
  4. Handle gracefully: Don’t assume inputType is always correct - have fallback logic
  5. Test across browsers: This issue varies significantly by browser and IME combination

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-0213-ime-beforeinput-insertcompositiontext-input-deletecontentbackward iOS 17.0 iPhone or iPad Any Safari 17.0 Korean (IME) draft

Cases

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

Related Scenarios

Other scenarios that share similar tags or category.

Tags: ime, composition, beforeinput, input

Selection mismatch between beforeinput and input events

The selection (window.getSelection()) in beforeinput events can differ from the selection in corresponding input events. This mismatch can occur during IME composition, text prediction, or when typing adjacent to formatted elements like links. The selection in beforeinput may include adjacent formatted text, while input selection reflects the final cursor position.

1 case
Tags: ime, composition, beforeinput

getTargetRanges() returns empty array in beforeinput events

The getTargetRanges() method in beforeinput events may return an empty array or undefined in various scenarios, including text prediction, certain IME compositions, or specific browser/device combinations. When getTargetRanges() is unavailable, developers must rely on window.getSelection() as a fallback, but this may be less accurate.

1 case
Tags: ime, composition, beforeinput

insertParagraph preventDefault breaks IME composition state in Safari

In Safari desktop, when preventDefault() is called on keydown or beforeinput events for insertParagraph (Enter key), the IME composition state becomes corrupted. Subsequent text input fails to trigger proper input events, causing characters to not be inserted or composition to malfunction.

3 cases
Tags: ime, beforeinput, input

iOS dictation triggers duplicate input events after completion

On iOS, when using voice dictation to input text into contenteditable elements, the system may fire duplicate beforeinput and input events after the initial dictation completes. The text is split into words and events are re-fired, causing synchronization issues. Composition events do not fire during dictation, making it difficult to distinguish dictation from keyboard input.

1 case

Comments & Discussion

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