Lesstep
Japanese forms · full-width digits · Checked October 29, 2026

Forms that reject numbers typed in Japanese

People in Japan fill in forms with the Japanese input method still on, and it types digits in their full-width form: 123, not 123. A postal code arrives as 123-4567, or 150ー0001, and a check written for ASCII says it is invalid. Normalize the value before you check it:

// ー ‐ – — ― and −, which NFKC leaves
const DASHES =
  /[\u2010-\u2015\u2212\u30FC]/g;

function toAsciiNumber(value) {
  return value
    .normalize("NFKC")
    .replace(DASHES, "-")
    .replace(/\s/g, "");
}

toAsciiNumber("150ー0001") returns "150-0001". Use it on number fields only (postal codes, phone numbers, quantities), not on names or free text.

Where the full-width digits come from

Mozc, the open-source project Google Japanese Input comes from, sets this in its default configuration: digits are full-width while a word is being composed, and after conversion they keep the form the person chose last time. Press Enter without converting, and 123 is what goes into your field. Its romaji table turns the hyphen key into ー (U+30FC), the long-vowel mark, not a hyphen.

A real report, from findadoc-web, a site for finding doctors in Japan: with a Japanese input method on, the facility form got 123-4567 and 090-1234-5678, the ASCII-only regular expressions rejected both, and the form could not be submitted. The report proposes normalizing with NFKC first, the way the same file already treats names written in Japanese.

What your check sees

The postal-code check /^\d{3}-\d{4}$/ in JavaScript (Node.js 24):

TypedRawAfter toAsciiNumber
123-4567passespasses
123-4567failspasses
150ー0001failspasses

In JavaScript, \d means 0–9 only, even with the u flag, and Number("123"), parseInt("123", 10) and parseFloat("1.5") all return NaN. A pattern="[0-9]{3}-[0-9]{4}" attribute fails the same way: in Chromium 152, validity.patternMismatch was true for 123-4567.

Python goes the other way. Its \d matches any Unicode digit, and int("123") returns 123, so 1234567 passes re.fullmatch(r"\d{7}", …) and is stored next to 1234567, and the two don't match when you search. Normalize on the server too:

import re
import unicodedata

DASHES = re.compile(
    "[\u2010-\u2015\u2212\u30fc]")

def to_ascii_number(value):
    value = unicodedata.normalize(
        "NFKC", value)
    value = DASHES.sub("-", value)
    return re.sub(r"\s", "", value)

Then check with ASCII digits only: re.fullmatch(r"\d{3}-\d{4}", value, re.ASCII).

Why the dashes need their own line

NFKC folds full-width forms back to ASCII: digits, letters, . and ,, the full-width space, and the full-width hyphen - (U+FF0D). It leaves alone the characters that are not width variants of ASCII: the long-vowel mark ー (U+30FC), the hyphen ‐ (U+2010) and the minus sign − (U+2212). We checked each one in Node.js 24. So "150ー0001".normalize("NFKC") is "150ー0001", and still fails.

That is why the dash line belongs in number fields only. In words, ー is a letter: it is the long sound in ユーザー ("user").

type="number" is not the fix

In Chromium 152, text inserted into type="number" (not through a real input method) came out converted: 123 became 123, and 1.5 became 1.5. But 090ー1234 left the digits on the screen and the value empty, with validity.badInput true: a script that reads input.value gets an empty string while the person sees their number. Setting value from a script to "123" also gave an empty string, as the HTML standard's sanitization rule says.

The HTML standard says type="number" "is not appropriate for input that happens to only consist of numbers", naming US postal codes, and suggests type="text", possibly with inputmode or pattern. For a phone number, type="tel" "does not enforce a particular syntax", so you still normalize:

<input type="text"
  inputmode="numeric"
  autocomplete="postal-code">

<input type="tel"
  autocomplete="tel">

inputmode="numeric" asks phones for a number keypad. On a computer it changes nothing, so the input method still decides what is typed.

Normalize after the composition ends

Don't rewrite the field while someone is typing into it. While a word is being composed, input events still fire, with isComposing true (UI Events). A filter that strips non-ASCII characters on every input event deletes full-width digits as they arrive (a report), and a value rewritten during composition can cancel it (another). Normalize on change, which fires when the field loses focus, and again right before you validate:

input.addEventListener("change", () => {
  const v = toAsciiNumber(input.value);
  input.value = v;
});

In React, onChange runs on every keystroke, so keep the raw text there and normalize in onBlur or in your submit handler. If you must filter as people type, skip events where event.nativeEvent.isComposing is true. The same check stops Enter from sending half a word (how).

Search boxes too

Normalize both sides of a search with NFKC, the query and the text you search in, so pg finds pg and half-width テスト finds テスト. Don't apply it to text you store and show as typed: NFKC also turns ① into 1 and ㈱ into (株).

The rest of a Japanese check

A form that rejects a postal code loses the person at the last step, after they decided to sign up or buy. The same pass catches Enter while typing Japanese (the fix), yen charged 100 times over (the fix), kanji drawn in their Chinese shapes (the fix), headings that break in the middle of a word (the fix), dates saved one day early in Japan (the fix), address forms in the American order (the fix), phone numbers sent to +81 with the 0 still in (the fix), PDFs with the Japanese missing (the fix), search that finds nothing in Japanese (the fix), previews cut down to “...” in Japanese (the fix), lists sorted in the wrong order (the fix), email subjects that arrive garbled (the fix), CSV files that open garbled in Excel (the fix), Japanese titles that give an empty URL slug (the fix), file names garbled on upload and download (the fix), dates in the Japanese era that come out wrong (the fix), full-width spaces that get past your checks (the fix), public holidays your app gets wrong (the fix), and vertical text that comes out sideways or upside down (the fix).

Lesstep translates web apps into Japanese and delivers them as one pull request. In Complete (US$249), we fill in every form with full-width digits and the dashes a Japanese input method types, and fix the checks, in the browser and on the server, in the same pull request. The first step is a free sample.

Google is a trademark of Google LLC; Python is a trademark of the Python Software Foundation; Node.js is a trademark of the OpenJS Foundation; React is a trademark of Meta Platforms, Inc. This page is not affiliated with or endorsed by any of them.

More: Enter sends the message while typing Japanese · Stripe charges 100 times too much in yen · Kanji in their Chinese shapes · Japanese headings that break in the middle of a word · Dates that come out wrong for Japanese users · Address forms in the Japanese order · Japanese phone numbers in forms · Japanese text missing from your PDFs · Search that finds nothing in Japanese · Length limits that break in Japanese · Japanese lists sorted in the wrong order · Japanese email subjects that arrive garbled · Japanese CSV files that open garbled in Excel · Japanese titles that give an empty URL slug · Japanese file names garbled on upload and download · Japanese era dates that come out wrong · Full-width spaces that get past your checks · Japanese public holidays your app gets wrong · Japanese vertical text that comes out sideways or upside down · Japanese localization checklist · Japanese localization for your web app