# Form fields that reject full-width digits without saying why

Source: https://easeweb.dev/learn/full-width-digits
Topics: Forms > Fields accept full-width and half-width characters (WCAG 3.3.1, 3.3.3)
The fix: Either convert the number before checking it, so full-width digits and hyphens become half-width ones and the form sends the same number, or, where the field has to take half-width digits only, say so in the error message, in place of a message that only calls the number invalid. Never delete characters from the field as they are typed.
Test with: input method, keyboard, screen reader
References: https://www.w3.org/WAI/WCAG22/Understanding/error-identification.html https://www.w3.org/WAI/WCAG22/Understanding/error-suggestion.html https://www.w3.org/WAI/WCAG22/Techniques/general/G85 https://www.w3.org/WAI/WCAG22/Techniques/general/G177 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize https://unicode.org/reports/tr15/ https://design-system.service.gov.uk/patterns/telephone-numbers/

This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.

## What happens

A shop's checkout asks for a phone number, so the courier can call. A customer types it with Japanese input on, as they were typing their address a moment before. In hiragana mode, a Japanese input method can type digits at full width, depending on its settings: ０１２３４５６７８９ rather than 0123456789. They are the same digits to anyone reading them, drawn a little wider, each one the width of a kana. A number copied from a document written in Japanese can be full-width too.

The field's script knows only the half-width digits 0 to 9, and sites write it in one of two common ways:

- It checks the number when the form is sent, and refuses it with "Enter a valid phone number." The customer reads the number back, finds the right digits, sends it again, and gets the same message. Nothing says that the number is right and only its width is wrong.
- It keeps the field to digits with an `input` handler that deletes everything else. Full-width digits are not 0 to 9 to that handler, so they vanish from the field as they are typed, with no message. A hyphen typed in hiragana mode comes out as ー, the long vowel mark, and goes the same way.

## Why it fails

The digits are not the error: the field refuses a value in a form it hasn't asked for, so the failure is in what the page says about it.

Success Criterion 3.3.3 Error Suggestion asks that when an input error is detected and a way to correct it is known, the correction is suggested. The page knows exactly what it wants, half-width digits, since that is the only thing its check tests for, and "Enter a valid phone number" doesn't say it. Its Understanding document names the people this is for: people with cognitive disabilities, who may find it hard to understand how to correct an error, and people with visual disabilities, who may not be able to work out exactly how. A width they can barely see is a correction neither group can guess.

Success Criterion 3.3.1 Error Identification asks that an input error the page detects is identified, and described to the person in text. The first Before meets it: the message names the field and says it is not valid, which the Understanding document accepts ("Email is not valid" passes 3.3.1). The second doesn't. Its handler detects the characters it won't accept and deletes them, and no text says so anywhere. The Understanding document covers a page that changes a value by itself: the error still has to be described to the person.

## Who is affected

Everyone who types Japanese can meet this, whatever their abilities: an input method that types full-width digits is a setting, not a mistake. Those listed below are the people for whom it costs the most.

## What automation and AI miss

The field has a label, the message is tied to it, and the page passes a scan: nothing in the markup is wrong. A scan never types, and someone testing without a Japanese input method only ever types half-width digits, so the check always passes. AI assistants asked for "a phone field that only accepts numbers" can write one of the two Befores below. EaseWeb's audit sets the full-width number into each field that asks for digits and sends an `input` event, then reports a field whose handler deletes it with no words about width (3.3.1), and a field whose `pattern` refuses it while the browser checks the form and nothing near the field names the width (3.3.3). The audit never sends a form, so on the parts of a page written in Japanese, other phone, postcode and kana fields are listed for a person to try.

## Before: a check that says only that the number is invalid

```html
<form>
  <label for="tel">Phone number</label>
  <input id="tel" name="tel" type="tel" autocomplete="tel"
    aria-describedby="tel-error">
  <p id="tel-error"></p>
  <button type="submit">Continue</button>
</form>
```

```js
const form = document.querySelector('form');
const tel = document.getElementById('tel');
const error = document.getElementById('tel-error');

form.addEventListener('submit', (event) => {
  const valid = /^[0-9]{10,11}$/.test(tel.value);
  tel.setAttribute('aria-invalid', String(!valid));
  error.textContent = valid ? '' : 'Enter a valid phone number.';
  if (!valid) event.preventDefault();
});
```

The check accepts only the half-width digits 0 to 9, so ０１２３４５６７８９ fails it, and the message says the number is invalid without saying that half-width digits would pass.

## Before: a handler that deletes everything but 0 to 9

```html
<form>
  <label for="tel">Phone number</label>
  <input id="tel" name="tel" type="tel" autocomplete="tel"
    aria-describedby="tel-error">
  <p id="tel-error"></p>
  <button type="submit">Continue</button>
</form>
```

```js
const form = document.querySelector('form');
const tel = document.getElementById('tel');
const error = document.getElementById('tel-error');

// Keep the field to digits.
tel.addEventListener('input', () => {
  tel.value = tel.value.replace(/[^0-9]/g, '');
});

form.addEventListener('submit', (event) => {
  const valid = /^[0-9]{10,11}$/.test(tel.value);
  tel.setAttribute('aria-invalid', String(!valid));
  error.textContent = valid ? '' : 'Enter a valid phone number.';
  if (!valid) event.preventDefault();
});
```

The same form, with one handler added. Every full-width digit is a character outside 0 to 9, so the handler deletes it as soon as it goes in, and nothing on the page says what happened or why.

## After: convert the digits, then check them

```html
<form>
  <label for="tel">Phone number</label>
  <input id="tel" name="tel" type="tel" autocomplete="tel"
    aria-describedby="tel-error">
  <p id="tel-error"></p>
  <button type="submit">Continue</button>
</form>
```

```js
const form = document.querySelector('form');
const tel = document.getElementById('tel');
const error = document.getElementById('tel-error');

form.addEventListener('submit', (event) => {
  // Full-width digits and hyphens become half-width; then spaces and dashes go.
  const digits = tel.value.normalize('NFKC').replace(/[\s\-‐−ー]/g, '');
  const valid = /^[0-9]{10,11}$/.test(digits);
  tel.setAttribute('aria-invalid', String(!valid));
  error.textContent = valid ? '' : 'Enter the 10 or 11 digits of the phone number, such as 0123456789.';
  if (valid) tel.value = digits;
  else event.preventDefault();
});
```

`normalize('NFKC')` is Unicode's compatibility normalization, and among other things it turns full-width digits, the full-width hyphen and the full-width space into their half-width forms, so ０１２３４５６７８９ becomes 0123456789. The replace then takes out spaces and the dashes people type between groups, ー among them, which normalization leaves as it is. The number is checked after that, so the same number passes in either width, and the field is given the converted digits, which the form sends. A number that is still wrong, such as one with nine digits, gets a message that says what to enter. There is no `input` handler, so nothing is deleted as people type. Convert the number the same way on the server, since a request can reach it without this script.

## After: say which digits to use

```html
<form>
  <label for="tel">Phone number</label>
  <input id="tel" name="tel" type="tel" autocomplete="tel"
    aria-describedby="tel-error">
  <p id="tel-error"></p>
  <button type="submit">Continue</button>
</form>
```

```js
const form = document.querySelector('form');
const tel = document.getElementById('tel');
const error = document.getElementById('tel-error');

form.addEventListener('submit', (event) => {
  const valid = /^[0-9]{10,11}$/.test(tel.value);
  const wide = /[０-９]/.test(tel.value);
  tel.setAttribute('aria-invalid', String(!valid));
  error.textContent = valid ? ''
    : wide ? 'The phone number has full-width digits. Type it in half-width digits (0 to 9).'
    : 'Enter the 10 or 11 digits of the phone number, such as 0123456789.';
  if (!valid) event.preventDefault();
});
```

The check is unchanged, and the message now says what is wrong and what to do: the number has full-width digits, and the field wants half-width ones. Any handler that deleted characters as they were typed is gone, so the digits stay where the person can see them and the message explains them. This passes both criteria, but the number is still refused, and the person has to switch their input method off and type it again. Say it beside the label as well, such as "Half-width digits", so people can type it that way the first time.

## Which option to use

Both pass 3.3.1 and 3.3.3. Converting the number is the one to use: the number was right, and converting it takes one line of code, no more than the second fix needs to notice the full-width digits, and the second fix spends its line on a message instead of on the number. Saying which digits to use is for a field whose rules you can't change, such as a form that posts to a system that refuses anything else; it leaves everyone who types in Japanese to type the number twice.

## Implementation decisions

Convert when the form is sent, or when the field loses focus, not in an `input` handler. While a conversion is in progress the text belongs to the input method, and a handler that rewrites the value then interferes with what the person is typing. If you do convert as people type, skip the events where `event.isComposing` is true, as the [guide on Enter during IME conversion](/learn/ime-composition) does for its key handler.

The first fix gives the field back the converted digits, so the form sends the number in the form you store. Where you show it back to the person, such as on a confirmation page, keep it recognisable: GOV.UK's telephone number pattern reports people confused when shown a reformatted version of the number they gave.

If the field has a `pattern` for the browser's own check, the browser tests the value as typed, before any script converts it. Either let the pattern take both widths (`[0-9０-９]{10,11}`) and convert on the server, or leave `pattern` off and check in the submit handler after converting. A `pattern` that takes only 0 to 9 brings back the first Before, with the browser's generic "match the requested format" message in place of yours.

Apply `NFKC` only to fields whose form you decide, such as phone numbers, postcodes and card numbers. It changes more than width: ① becomes 1 and ㈱ becomes (株), which is not what someone meant in a name or an address. Python's `unicodedata.normalize`, Java's `Normalizer` and PHP's `Normalizer` (in the intl extension) all offer the same NFKC form for the server.

`type="tel"` and `inputmode="numeric"` bring up a keypad on phones, which types half-width digits, but a number that is pasted, or typed on a computer with a Japanese input method on, can still be full-width, so the field still converts it.

## Verify the fix

1. With a Japanese input method on, in hiragana mode, type the phone number in full-width digits and send the form. It is accepted, or the message says to use half-width digits.
2. Type it again with hyphens between the groups, in hiragana mode, so they come out as ー. It is accepted, or the message says what to type instead.
3. Paste ０１２３４５６７８９ into the field and send the form. The digits stay in the field until it is sent, and the result is the same as in step 1.
4. Type a number with nine digits and send the form. The message says how many digits to enter.
5. Send the full-width number to the server without the page's script, such as with the script turned off or from a test request. The server converts it, or refuses it with a message that names the width.
6. With a screen reader running, send the full-width number. Focus the field: its message is read with it and says what to change.

Record the OS, browser, input method and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the shared form field or its validation changes.

## Limits

This is a starting pattern, not a guarantee for every application. The example is one phone field with a fixed number of digits; postcodes, card numbers and amounts need their own rules after the conversion, and a number with a country code needs a + kept. The replace takes out spaces and dashes, not brackets, so a number written as 03（1234）5678 gets the message; add brackets to it if your customers write them. A kana field that refuses hiragana or half-width katakana is the same failure: NFKC turns half-width katakana into full-width, and each hiragana letter becomes its katakana by a fixed step in Unicode, but that field has no guide of its own yet. How a message reaches people after a failed submission, by focus or by an error summary, is covered in [form errors that don't say how to fix them](/learn/accessible-form-errors).

---

Get this guide in your coding agent: MCP https://easeweb.dev/mcp (setup at https://easeweb.dev/services/developers)
