skip to content

A Vue 3 newsletter form previews a display name through v-model, but for Japanese users the preview lags until each word is confirmed — why, and how do you fix it?

level: seniorimportance: nice to knowfreq 26%

answer

  1. input method editor
  2. composition start and end events
  3. one sync when composing ends
  4. own listener plus a value binding

basics

~20 s

Vue 3's text v-model skips input events while an IME composition is active and syncs once when it ends. For live updates during composition, replace v-model with a :value binding plus your own @input handler.

solid answer

~40 s

Japanese, Chinese and Korean users type through an **IME**, which builds a character over several keystrokes before committing it. Vue's text `v-model` listens for `compositionstart` and `compositionend`: while a composition is open it ignores `input` events, and when it ends it dispatches one more `input` so the committed text reaches the model. That is deliberate — the half-built text is not what the user means — but it makes a live preview lag. The Vue guide's fix is to drop `v-model` and use `:value` plus your own `@input` listener, which sees every intermediate value. Then do only cheap, non-reformatting work during composition; `.lazy` does not help, because it waits even longer.

go deeper

for a junior

Know that v-model waits for an IME composition to finish before updating, and that this is intended behaviour.

for a middle

Explain the compositionstart and compositionend handling, the single synthetic input at the end, and the documented :value plus @input workaround.

for a senior

Diagnose IME-only lag in production, apply the manual binding only where live feedback matters, and keep expensive work and reformatting off intermediate text.

for a principal

Make IME behaviour part of the form library's contract, so shared inputs document which fields see intermediate text and test with a real IME.

## What an IME is and why it matters An **input method editor (IME)** lets a user type characters that a keyboard cannot produce directly. A Japanese user types romaji, sees kana appear underlined, converts them to kanji, and confirms. Until confirmation the field holds a **composition**: text that is visible in the field but not yet final. The browser signals this with `compositionstart`, a series of `input` events, and `compositionend`. ## What Vue's v-model does during a composition On text inputs and textareas, Vue 3's `vModelText` directive handles composition explicitly: 1. On `compositionstart` it flags the element as composing. 2. Every `input` event checks that flag; while it is set, the model is **not** written. 3. On `compositionend` it clears the flag and dispatches a synthetic `input` event, so the committed text is written once. 4. A `change` listener also ends the composition, for browsers that skip `compositionend` when focus moves away mid-composition. 5. On re-render, if the element is still composing, Vue does **not** patch the field's value, so an unrelated update cannot wipe the uncommitted text. The Vue guide summarises it: `v-model` does not get updated during IME composition, and if you want to respond to those updates you should use your own `input` listener and `value` binding. ## Why the newsletter preview lags The preferences form shows "Your emails will be signed to: …" under the display-name field. With `v-model="displayName"`, a user typing 山田 sees the field fill in immediately, but the preview only changes when each conversion is confirmed. For a Latin-script user the preview updates per keystroke; for an IME user it updates per confirmed word. Nothing is broken; the binding is doing what it is designed to do. ## The fix, and its costs Replace `v-model` with a one-way value binding and your own listener: ```vue <script setup lang="ts"> import { ref } from 'vue' const displayName = ref('') function onInput(e: Event) { displayName.value = (e.target as HTMLInputElement).value } </script> <template> <input :value="displayName" @input="onInput" /> <p>Signed as: {{ displayName }}</p> </template> ``` Now every `input` event, including those inside a composition, writes the model. This changes what the rest of the component sees: - **Intermediate text reaches the model.** Watchers and computeds receive kana that will become kanji a moment later. Anything expensive (a server availability check, analytics) should not run on these values; the DOM `InputEvent.isComposing` flag lets a handler tell them apart. - **Do not rewrite the field during composition.** Reformatting or trimming the value while the IME is open can break the conversion. Keep formatting for the committed value. - **You lose the modifiers.** `.trim` and `.number` are `v-model` features; with a manual listener you trim or cast yourself. ## Options compared | Approach | Model during composition | Model after confirmation | Good for | |---|---|---|---| | `v-model` | unchanged | committed text | most fields, validation, submission | | `v-model.lazy` | unchanged | updated only on `change` (blur / Enter) | expensive fields; makes the lag worse | | `:value` + `@input` | every intermediate value | committed text | live previews, character counters | ## How to tell this apart from a real bug - The lag appears only for IME users, never for Latin-script typing. - The model catches up exactly when the user confirms a conversion. - A `watch` on the model logs one change per confirmed word rather than per keystroke. If the model never catches up, the problem is elsewhere — for example code overwriting the ref, or a binding on the wrong element.

  • Does v-model.lazy help Vue 3 users typing through an IME?
    No — it makes updates later, not sooner. `.lazy` syncs on `change`, which for a text field fires when the edit is committed, typically on blur or Enter. The composition handling becomes irrelevant, but the preview then waits for the whole field, not just each word.
  • Why does Vue 3's text v-model skip patching the field while a composition is active?
    So a re-render triggered by anything else cannot overwrite text the IME has not committed yet. Writing `el.value` in the middle of a composition would replace the in-progress characters and break the conversion, so Vue leaves the element alone until the composition ends.

v-model under an IME is a court stenographer who writes down a witness's answer only when the witness finishes the sentence, never the half-words while they search for the right term. A live preview needs a second listener who repeats every half-word.

saying these in an interview costs you the question

  • v-model has a bug with Japanese input and needs a library workaround.
  • Adding .lazy makes v-model update during IME composition.
  • Vue pauses all reactivity while an IME composition is open.
  • A manual @input listener behaves exactly like v-model, modifiers included.
  • It is safe to reformat the field's value on every input event, composing or not.