A report comes in saying the search box "goes mad" while they type, or that the form will not accept their name, or that the character counter is wrong. You open the page, type the same thing, and everything is fine.
They are using an input method editor. You are not.
What a composition session is
An IME is how you type a language with more characters than keys. You type several keystrokes, the system shows a provisional string, and at some point you commit it and the real text lands. Japanese, Chinese and Korean most obviously, but the same machinery covers something far more common than that, which I will come back to.
The W3C UI Events specification calls this a composition session, and describes it as "one compositionstart event, one or more compositionupdate events" and "one compositionend event".
So far so reasonable. The part that breaks code is what happens inside the session.
Your input listener fires on the provisional text
Section 3.6.6 of the spec is titled "Input Events During Composition", and it sets the order: for each update, beforeinput, then compositionupdate, then input.
So input fires repeatedly, during the session, carrying text the user has not committed and in many cases would not recognise as anything they typed.
And then the sting, from the same section:
Since there are no DOM updates associated with the compositionend event, beforeinput and input events should not be sent at that time.
Read that twice if you have ever written field.addEventListener('input', validate). Your handler runs several times on provisional fragments, and then does not run at the moment the user finishes.
The practical results are all the ones in that bug report. A validator rejecting a half-formed string. A character counter counting the wrong thing. A search-as-you-type sending a request per keystroke of a word that does not exist yet. An autosave storing a fragment.
The flag that fixes it
Both InputEvent and KeyboardEvent carry isComposing, defined as:
true if the input event occurs as part of a composition session,
and, completing the sentence, "i.e., after a compositionstart event and before the corresponding compositionend event." Its uninitialized value "MUST be false", so on ordinary typing it is simply false and costs you nothing.
field.addEventListener('input', (e) => { if (e.isComposing) return // provisional text, not a value yet handle(field.value) }) // and because no input event arrives at the end of a session: field.addEventListener('compositionend', () => handle(field.value)) Two listeners. The second one is the half people leave out, and leaving it out means an IME user's committed text is never processed at all.
The advice you will find, and why it is wrong
Search this problem and you will be told to check for keyCode === 229.
That does appear in the specification. It appears in section 7.3.1, under a heading that says, in the document's own words, "This section is non-normative", in a legacy appendix describing one historical model. The same document says plainly:
Authors SHOULD use the key attribute instead of the charCode and keyCode attributes.
It also notes that keyCode's values "are not normatively defined in this specification". So the famous number is not a rule, it is a description of what some implementations used to do. isComposing is the attribute that is actually specified for this, and it is on the event you are already handling.
One more correction while here: it is often said that key events stop during composition. The spec says the opposite. "During the composition session, keydown and keyup events MUST still be sent," and those events "MUST have the isComposing attribute set to true".
Why this is not only a CJK problem
Here is the line that should widen your idea of who is affected. The spec describes isComposing on the input event as true when the event is dispatched
during a dead key sequence or while an input method editor is active
Dead keys. A French, Czech, or Vietnamese layout, or a US-International layout, produces accented characters by a dead key followed by a letter. That is a composition session too. Anybody typing é, ñ, ô or ü can hit the same code path, on the same keyboard hardware you have.
Which is the honest reason this survives so long in so many codebases. It is not exotic. It is simply invisible from a machine configured the way yours is, and the people who hit it describe it in the only words available to them: it does not work when I type my name.
That sentence contains the entire bug and explains none of it, and no amount of re-reading the ticket will get you to a dead key.
Specification quotes are from the W3C UI Events Working Draft of 21 February 2026, which asks to be cited as a work in progress.

