IME composition text moves outside empty table cell
OS: macOS 14.4 · Device: Desktop Any · Browser: Safari 17.4 · Keyboard: Japanese IME
Open case →Scenario
A technical evaluation of why IME composition often fails when anchored inside empty table structures.
Table cells (<td>, <th>) are unique in the DOM because they act as both layout containers and structural boundaries. IME (Input Method Editor) sessions require a stable Selection range to manage the “pre-edit” text. In many browsers, particularly WebKit-based ones, if a cell is empty or contains only a <br> or a zero-width space, the engine’s internal selection-mapping logic can “overshoot” the cell boundary during the commit phase, placing the final text in the parent row or before the table entirely.
In an empty cell, the browser often represents the caret as being “at the start of the cell”, but logically it might resolve to the same point as “before the table”.
/* Observed Sequence */
// 1. User focuses <td></td>
// 2. User types 'G' (IME start)
// 3. Browser creates a temp span for 'G'
// 4. User presses Enter (Commit)
// 5. Browser destroys temp span
// 6. Browser inserts 'G' but uses a cached range that is no longer validly nested.
<td> ignores cell padding, alignment, and scoping rules.<p> or <br> to maintain cell height/focus.Ensuring the cell is never technically “empty” is the most common fix.
function ensureTdContent(td) {
if (td.childNodes.length === 0) {
// Add a ZWSP to anchor the selection
td.appendChild(document.createTextNode('\u200B'));
}
}
compositionendOverride the browser’s insertion by intercepting the final commit.
element.addEventListener('compositionend', (e) => {
const selection = window.getSelection();
const range = selection.getRangeAt(0);
const td = findParentTd(range.startContainer);
if (!td) {
e.preventDefault();
console.warn('Fixing selection leak...');
forceInsertIntoCorrectCell(e.data);
}
});
contenteditable table cell completely empty.cell.contains(range.commonAncestorContainer).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-0566-safari-table-cell-composition-leak | macOS 14.4 | Desktop Any | Safari 17.4 | Japanese IME | confirmed |
| ce-0575-prosemirror-safari-empty-table-leak | macOS 15.2 | Desktop Any | Safari 18.2 | Japanese IME | confirmed |
This matrix shows which browser and OS combinations have documented cases for this scenario. Click on a cell to view the specific case.
| Browser | macOS |
|---|---|
| Safari |
Open a case to see the detailed description and its dedicated playground.
OS: macOS 14.4 · Device: Desktop Any · Browser: Safari 17.4 · Keyboard: Japanese IME
Open case →OS: macOS 15.2 · Device: Desktop Any · Browser: Safari 18.2 · Keyboard: Japanese IME
Open case →Other scenarios that share similar tags or category.
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.
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.
When using IME to input CJK text in heading elements (H1, H2, etc.) in WebKit browsers, pressing Space to confirm composition causes both the raw Pinyin buffer AND the confirmed characters to appear together.
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.
This case describes a baseline scenario for inspecting how a plain `contenteditable` region behaves
Have questions, suggestions, or want to share your experience? Join the discussion below.