日本語の「変換確定のEnter」で送信される事故を、3つの層で止めた話
変換を確定するつもりのEnterで、フォームが送られる。日本語を打つ人は全員踏む事故を、書き方・2枚重ね・機械の検査の3つの層で止めた記録です。
日本語で「せんたくそう」と打って、変換を確定するつもりでEnterを押したら、フォームが送信されてしまった。日本語を打つ人なら、一度は踏んでいるはずの事故です。
自分の案件でも同じことが何度も出て、「Enterに動作を割り当てたら、必ず変換中かどうかを見る」を決まりにしました。この記事は、その決まりの中身と、決まりだけでは足りなかった所の話です。
1何が起きるのか
日本語入力では、変換中の文字(未確定の下線がついた状態)に対して、Enterは「これで確定」の操作です。ところが、入力欄のEnterに「送信」「検索」「生成」を割り当てていると、確定のつもりのEnterがそのまま送信になります。
営業用のチャットウィジェットで直したときは、症状が2つ出ていました。確定前の文字が途中で送られてしまうことと、送信したあとに入力欄へ文字が残ること。2026-07-14 のコミットで、この2つは同じ根っこ(変換中のEnter)だと分かって、まとめて直しています。
2基本の形は1行
素のJavaScriptなら e.isComposing と e.keyCode !== 229 の両方を見ます。Reactなら e.nativeEvent.isComposing です。実際に入れているのは、たとえばこういう1行でした。
ここがポイント
if (e.key === "Enter" && !e.isComposing && e.keyCode !== 229) { e.preventDefault(); send(input.value); }
その8日後の 2026-07-22 には、営業リストを絞り込む検索窓にも入れました。含めたい語と除きたい語を打つ機能で、日本語を打つのは当たり前なので、最初から !e.isComposing && e.keyCode!==229 を付けています。
3Shift+Enter の改行と組み合わせる
チャット画面では、Enterで送信、Shift+Enterで改行、にしたくなります。この場合は3つを全部見ます。
e.key === "Enter"!e.shiftKey(Shift+Enter は改行のまま残す)!e.nativeEvent.isComposing(変換確定のEnterは送信にしない)
手元のコードでは、チャットの入力欄にこの3点セットが並んでいます。関数の頭で isComposing なら return してしまい、そのあとで Enter と Shift を見る書き方もあって、どちらの形でも後述の検査は通ります。textarea のEnterは改行が本来の動きなので、フォーム全体で止める側の処理を書くときは、textarea を素通しにしておかないと、改行まで死にます。
4229 だけを見る書き方は、再現ですり抜けた
ここで1つ、定番の説明を確かめようとしました。変換中のキー入力は keyCode が229で届く、というのがよく見る説明です。
CDP(ブラウザを外から操作する仕組み)の Input.imeSetComposition で変換中の状態を作ってからEnterを送ると、実際にはこう届きました(2026-08-31・Chromium・このサイトの問い合わせフォームで測定)。
Enterを送ったときの keydown の中身(Chromium・CDPで再現。keyCode は送る側で指定した値)
| 操作 | key | keyCode | isComposing | 止めたか |
|---|---|---|---|---|
| 変換確定のEnter | Enter | 13 | true | ◎止めない(これが正しい) |
| 素のEnter | Enter | 13 | false | 暗黙送信を止めた |
この再現では、確定のEnterでも keyCode は13でした。ただ、この13は送る側(CDP)で自分で指定した値です。本物のIMEで229が届くかどうかは、まだ確かめていません。それでも、229だけに頼る書き方だと、この再現ではすり抜けました。だから isComposing を主にして、229は保険として併用しています。
5フォームでは keydown だけだと足りなかった
ここからが、今回いちばん時間を使った所です。この問い合わせフォームに、keydown のガードだけを入れて確かめたところ、確定のEnterで送信が飛びました。
理由は、単一行の input に備わっている「Enterで送信」の動き(暗黙送信)です。keydown で変換中のEnterを素通ししたら、それを止める人が誰もいなくなる。素通しにしたのは、preventDefault() を呼ぶと確定の操作そのものを壊しかねないからで、そこの判断は今も正しいと思っています。
イベントの順番を測ると、こうでした。
- compositionstart
- compositionupdate
- input
- keydown(isComposing=true・keyCode=13)
- submit
- compositionend
submit が compositionend より先に来ます。だから submit の処理の頭で「いま変換中か」を見れば、時間の猶予も閾値もなしに確実に落とせます。2枚重ねにしました。1枚目は keydown で、素のEnterだけ preventDefault()。2枚目は submit の先頭で composingRef.current が true なら捨てる。2026-09-01 のコミットで、問い合わせフォーム(お問い合わせ)に入っています。
落とし穴が3つありました。
defaultPreventedを同じイベントの中で読むと、必ず false になる。React より先に付けた自分のリスナが先に走るためで、これで「ガードが効いていない」と誤診しました。判定には、submitの回数と、実際にAPIへ飛んだ回数を使うのが安全です- ページがまだ動いていない状態で検証してしまうと、存在しないバグを2件観測しました。ビルドの出力を2つのプロセスで共有していて、配信中のHTMLが消えたチャンクを指していたのが原因でした。ビルドも200もスクショも通るのに、JSは1行も動いていません
- 守りすぎると、送信ボタンを押しても送れないフォームになる。変換したまま送信ボタンを押したら、ちゃんと送れることも測りました(APIに1件、完了画面1回)
6目視では守れないので、機械で検査する
決まりを書いても、次のEnterを書くときに忘れます。なので、リポジトリ全体を検査するテストを足しました。onKeyDown やキーダウンのリスナを持つファイルを集めて、Enter を判定している箇所の前後400文字に isComposing か229があるかを見るだけの、単純なものです。
本当に落ちるのかを、別のフォルダで確かめました。isComposing の無いEnterの処理を1つ置くと、テストは「pass 0 / fail 1」で、components/Bad.tsx:2 を名指しして落ちる。!e.nativeEvent.isComposing を足した版に差し替えると「pass 1 / fail 0」。落ちるのが先、通るのが後、の順で確かめました。
最初は、role="button" や <button> のEnterまで拾って、誤検出が1件出ました。文字を打たない要素ではIMEが働かないので、除外しています。
この検査で守れるのは「書いてあるか」までです。400文字以内に isComposing という字があれば通るので、本当に効いているかの証明にはなりません。効いているかは、次の手順で人が測ります。
7入れたら通す6項目
日本語入力欄にEnterの動作を足したら、完了と言う前にこの6つを通すことにしています。
- 変換確定のEnterで送信されない
- メール欄でも同じ
- 素のEnterでも送信されない(暗黙送信を止める場合)
textareaのEnterは改行のまま- ボタンのクリック・Enterで、1回だけ送信される
- 候補チップのEnterは、挿入であって送信ではない
CDPなら、page.keyboard.insertText では変換中の状態が作れないので、Input.imeSetComposition で未確定の文字を置いてから Input.dispatchKeyEvent でEnterを送ります。送ったあと200ミリ秒ほど待たないと、結果が空のまま読めてしまいます。
8まだ分からないこと
正直に言うと、測ったのはChromiumだけです。SafariやiPhoneの実機、WindowsやAndroidのIMEでも、確定のEnterが同じ値で届くのかは、まだ分かりません。ここまでの2枚重ねは、composingRef で「変換中か」を自分で持っているので、ブラウザごとの違いには強いはずですが、強いはず、と、測った、は別の話です。
それに、CDPで作った変換中の状態は、本物のキーボードと完全に同じとは限りません。実機の確認は、まだ済んでいません。
次は、iPhoneの実機で同じ6項目を通して、結果をこのブログに追記します。それまでは、自分のフォームで「ひらがなを打つ→変換する→Enterで確定する」を、ぜひ一度試してみてください。送信されたら、そこが直す場所です。ほかの記事は ブログ にあります。
この記事を書いた人
NextCode(沖縄県宜野湾市)は、iOSアプリ・ホームページ/LP・NFCデジタル名刺・AI業務自動化を作っている開発スタジオです。ここで書いているのは、実際に作って公開したものと、その過程で詰まったことだけです。
お問い合わせフォームは24時間受け付けています。