Text measurement methods
Count Characters: Visible Units, Code Points and Bytes
Compare visible characters, UTF-16 units, Unicode code points and UTF-8 bytes with tested JavaScript examples for emoji and accented text.
There is more than one useful way to count characters. A visible symbol can contain multiple Unicode code points, JavaScript string units or encoded bytes. Choose the unit required by the receiving system before comparing a total with a limit. For ordinary ASCII text the counts often agree, which can hide the distinction until an emoji or accent appears.
Compare the units on a small example
For the string Aš, JavaScript reports three UTF-16 code units, two Unicode code points and two grapheme clusters. UTF-8 encoding uses five bytes. These four totals describe the same unchanged text. The emoji takes a surrogate pair in UTF-16 and four bytes in UTF-8.
const text = "Aš";
const segments = new Intl.Segmenter("en", { granularity: "grapheme" });
console.log({
utf16Units: text.length,
codePoints: Array.from(text).length,
graphemes: Array.from(segments.segment(text)).length,
utf8Bytes: new TextEncoder().encode(text).length
});
// { utf16Units: 3, codePoints: 2, graphemes: 2, utf8Bytes: 5 }Choose the metric that answers the requirement
For a user-facing writing limit, the destinationās own counter is the practical authority. A database column, SMS service or API may use another unit. Read its documentation rather than assuming the word character means the same thing everywhere. A byte limit is especially different from a visible-character limit.
A grapheme cluster is a useful approximation of a user-perceived character. It is not a claim that every language, font and application treats the text identically. Complex emoji sequences and combining marks are examples where a simple split into code points is insufficient for a visible-unit count.
Test a decomposed accent
const text = "eĢ"; // e followed by a combining acute accent
const segments = new Intl.Segmenter("en", { granularity: "grapheme" });
console.log(text.length, Array.from(text).length,
Array.from(segments.segment(text)).length,
new TextEncoder().encode(text).length);
// 2 2 1 3This looks like Ć©, but its stored sequence differs from the precomposed form. Normalizing it to NFC produces one code point and two UTF-8 bytes for this example. Its visible grapheme count remains one. Preserve the original when storage identity matters; normalization is a transformation, not merely a display preference.
Define whitespace handling separately
Characters including spaces includes spaces in the chosen unit. A label saying excluding spaces needs more detail: does it remove only ordinary spaces, or also tabs and line breaks? The local counterās no-spaces metric removes whitespace matched by its JavaScript whitespace rule after normalization. That is broader than removing only U+0020.
For Hi, A1!, seven ordinary characters include the comma, space, digit and exclamation mark. Removing the one ordinary space leaves six. A letters-only count is three. Use this short sample to verify the label you are reading before measuring a long application response.
Use the local count as a documented cross-check
The Character Counter normalizes text to NFC and uses browser grapheme segmentation where available, with a code-point fallback in older environments. It is useful for reviewing copied text, but it cannot determine a remote formās undocumented counting convention.
After pasting into the final destination, check its own displayed total and confirm the beginning and end of the text. A missing last paragraph can make a paste appear to fit a limit while losing content. Do not remove accents or replace names just to reduce a byte count without reviewing the resulting meaning.
Truncate at the intended boundary
Do not shorten a string by an arbitrary byte or UTF-16 index when a complete visible character must remain intact. That can separate a surrogate pair or a combining sequence. Segment first using the intended unit, keep the permitted complete segments, and then verify the encoded size if the destination also imposes a byte limit.
JavaScript references
String length and Intl.Segmenter explain the relevant units. The numerical examples here are executed checks, not estimates.


