You type your name into a form. It stops accepting letters halfway through. The field says forty characters and you have not typed forty characters — you have typed about twenty.
This happens constantly to people with Khmer, Burmese, Tamil, Lao and Thai names, and almost never to people with English names. The culprit is a mismatch between what a computer counts and what a person sees — worth understanding whether you are the one filling the form in or the one who built it.
Two different things called a character
When a web form says maxlength="40", the browser counts UTF-16 code units. That is a storage unit, not a letter.
What a person calls a character, typographers call a grapheme cluster — the single visible unit that occupies one position as you read. In English these are the same, so nobody notices. In the scripts of mainland Southeast Asia they are not, because a written syllable is built from a base letter plus vowel signs and tone marks stacked around it, and each of those pieces is its own code unit.
Taking Article 1 of the Universal Declaration of Human Rights in each language — one sentence, same meaning throughout — here is how many code units each visible character actually costs:
| Language | Code units per visible character | Characters you get in a 20 field | …in a 40 field |
|---|---|---|---|
| Khmer | 1.87 | 10 | 21 |
| Burmese | 1.65 | 12 | 24 |
| Tamil | 1.52 | 13 | 26 |
| Lao | 1.35 | 14 | 29 |
| Thai | 1.25 | 15 | 31 |
| Vietnamese | 1.17 | 17 | 34 |
| English, Malay, Indonesian, Tagalog, Chinese | 1 | 20 | 40 |
A field advertising twenty characters gives a Khmer speaker ten. The form is not lying; it is counting something the person filling it in cannot see.
Vietnamese has a second, sneakier version of this
Vietnamese letters carry two marks at once — one for vowel quality, one for tone. Unicode can store these in two ways. The precomposed form uses a single code point for the whole letter; the decomposed form stores the base letter and each mark separately.
Both display identically. They are not the same length. Measured on the same sentence, the decomposed form is 31.7% longer in code units than the precomposed one.
So the same Vietnamese name can pass a length check on one device and fail it on another, depending on what the keyboard produced — and macOS and Windows have historically differed here. If you have ever had a form accept a name once and reject it the next day, this is a plausible culprit. The fix is to normalise to NFC on the way in, before you measure anything.
We checked our own forms
It would be comfortable to write this as somebody else's bug. So we counted ours.
Across our 905 tool widget templates we found 106 maxlength attributes. To get an honest count we had to sort them by function:
- 3 are numeric or date inputs, where a length cap is simply correct.
- 68 are domain-constrained — a chemical element symbol capped at two, a Chinese surname box capped at two, a MAC address prefix capped at nine. Counting these as defects would have produced an alarming number and a false one.
- 35 genuinely accept free human text. Of those, 17 are capped at 40 code units or fewer.
Those seventeen include name fields in our family tree builder and in several name generators, where people are most likely to be typing a non-English name. At forty code units, a Khmer name gets twenty-one visible characters. That limit, which we found by auditing rather than assuming, is now on our list to raise.
The classifier reads intent from each field's id, name and placeholder, so a free-text field with an opaque name would have been filed under domain-constrained. Thirty-five is therefore a floor, not a ceiling.
If you build forms
- Normalise to NFC before you measure or store. One line, and it removes the Vietnamese class of bug entirely.
- Do not cap name fields at forty. There is rarely a real reason; if your column is
VARCHAR(255)then let the field be generous and validate on the server. - Count graphemes when the limit is for humans.
Intl.Segmenteris in every current browser and gives you the number the user is actually seeing. - Count bytes when the limit is for a database. A three-byte script fills a byte-limited column three times faster, and that truncation happens silently on the server where nobody sees it.
- Test with a real name in a real script. Not "test test" — paste in Khmer or Burmese and watch what your field does.
This counting problem has a second face in AI pricing, which charges by the token: scripts that need several code units per character can cost multiple times more to process than English. We measured that too, in what the same sentence costs in eleven languages — and the more basic question of whether the page can render the script at all is why some languages come out as empty boxes.
What is measured here
The per-language ratios are measured, on 20 August 2026, from Article 1 of the Universal Declaration of Human Rights in each language, taken from the Unicode Consortium's repository. Visible characters are counted with Intl.Segmenter at grapheme granularity. English is used as a control: it must measure exactly 1.00 code units per visible character, and the script refuses to run if it does not.
The audit of our own fields is a count of what is in our templates, not a test of what happens in a browser. It tells you a cap exists and how tight it is; it does not prove a specific user was truncated. Ratios come from one formal sentence per language, so a different name would give slightly different numbers — the ordering, and the fact that a forty-character field is not forty characters for most of Southeast Asia, do not depend on the sample.