Four ways to count one character
A grapheme is what a reader calls one character; a code point is one Unicode value; a UTF-16 unit is what JavaScript’s .length counts; a UTF-8 byte is what a database column and an HTTP header measure. For plain English all four are the same number, which is why the difference stays invisible until it breaks something.
The family emoji 👨👩👧👦 is 1 character, 7 code points (four people joined by three zero-width joiners), 11 UTF-16 units and 25 UTF-8 bytes. The Korean syllable 한 is 1, 1 and 1 — but 3 UTF-8 bytes. Typed on macOS it often arrives decomposed as ᄒ + ᅡ + ᆫ: still 1 grapheme, but now 3 code points and 9 bytes for the same visible character.
This is why “20 characters” is not a specification. A form that measures graphemes accepts a name that a VARCHAR(20) column measuring bytes rejects, and a JavaScript check on .length disagrees with both. All four counts are shown at once so you can pick the one the receiving system actually uses.
Which count your system is actually using
- Graphemes: what people count, and what a well-built form or a designer’s character limit should count.
- Code points: Python’s len(), Swift’s unicodeScalars, and the CHAR/VARCHAR length of MySQL’s utf8mb4 and PostgreSQL.
- UTF-16 units: JavaScript .length, Java String.length(), C# string.Length, and the SMS UCS-2 count — an emoji is 2 here, which is where “my 140-character field ate half my emoji” comes from.
- UTF-8 bytes: Go’s len(string), file sizes, HTTP header limits, JSON payload sizes, and Oracle’s VARCHAR2(n BYTE).
One count is deliberately missing: legacy Korean systems that store EUC-KR (CP949) give a Hangul syllable 2 bytes rather than the 3 it takes in UTF-8, so a 100-byte EUC-KR field holds 50 Hangul characters and the same field in UTF-8 holds 33. This tool measures UTF-8 only — for an EUC-KR limit, count 2 bytes per Hangul or CJK character and 1 per ASCII character by hand.
SMS: why 160 characters becomes 70
A text message is encoded with the GSM 03.38 alphabet — 128 slots covering ASCII plus a few European letters and symbols — which packs 7 bits per character and fits 160 of them in one segment. Nine characters live in an extension table (^ { } \ [ ~ ] | €) and cost two slots each, so a message full of curly braces runs out at 80.
One character outside that set — an emoji, a curly quote pasted from Word, an em dash, any Korean or Japanese character — switches the entire message to UCS-2, which stores 16 bits per character and fits only 70. Nothing is wrong with the rest of the text; the encoding is a property of the whole message. The tool names the first characters that forced the switch, so a single stray “…” is easy to find and replace.
Past one segment each part gives up room to a concatenation header: 153 characters per segment in GSM-7 and 67 in UCS-2. So 161 characters is not “one and a bit messages”, it is two, and a 300-character Korean message is five segments, not three. Carriers bill per segment, which is why the counter shows segments rather than a single length.
X (Twitter) counts with weights
X does not count characters, it counts weighted units up to 280. Characters in the ranges U+0000–U+10FF, U+2000–U+200D, U+2010–U+201F and U+2032–U+2037 — Latin, Cyrillic, Greek, Hebrew, Arabic, common punctuation — weigh 1. Everything else, which means Korean, Japanese, Chinese and every emoji, weighs 2.
So a Korean post really does stop at about 140 characters while an English one reaches 280, and this is deliberate rather than a bug — the weighting was introduced when the limit doubled in 2017, precisely because CJK writing carries more meaning per character.
Links are the one place the meter reads high. X replaces every URL with a t.co link of exactly 23 characters no matter how long the original is, so a post carrying a 90-character URL has 67 more characters of room than the counter shows. A link in the text raises a note for that reason.
The limits in the preset list
- X 280 weighted units · Bluesky 300 graphemes · Instagram caption 2,200 · og:title 88
- SMS 160 in GSM-7 or 70 in UCS-2 — the meter switches to whichever encoding your text needs
- Meta description 155 · title tag 60 · Google Ads headline 30 · Google Ads description 90
Treat the two search ones as rules of thumb rather than hard limits: Google truncates a snippet by pixel width, not by character count, so a title of wide capitals is cut earlier than 60 lowercase characters and Korean text is cut earlier still. Staying at or under those numbers is what keeps a title intact on both desktop and mobile.
The Custom limit field takes any number and adds a meter of its own — useful for a CMS field, a product title cap or an internal style rule. Trim to limit cuts the text on grapheme boundaries, so it can never leave half an emoji or a stranded combining mark behind.
Normalization, invisible characters and broken emoji
Korean and accented Latin text can be stored two ways: precomposed (NFC) or decomposed (NFD). Text typed on macOS, and text pulled out of some PDFs, often arrives decomposed — 한 as three jamo instead of one syllable. It looks identical and it sorts and searches differently. Normalize (NFC) rewrites it and the tool shows how many bytes that saved: for a 100-character Korean string, 600 bytes down to 300.
An unpaired surrogate — half of an emoji, left behind when some other system truncated a string by UTF-16 units — is flagged separately. It has no visible form, it becomes U+FFFD (3 bytes) the moment it is encoded to UTF-8, and it is the usual reason a string that looked fine arrives as a black diamond.
Pasted text also carries invisible passengers: non-breaking spaces from a web page, soft hyphens from a PDF, zero-width joiners from an emoji sequence. They count toward every limit and none of them look like anything. If a count is higher than the text appears to justify, the “no spaces” figure next to the total is the quickest way to see how much of it is whitespace.
Limits and privacy
Above 100 KB the grapheme walk is replaced by code-point counting and an “approximate” badge appears. The two counts differ only where emoji sequences and combining marks appear, so for a large plain-text document the number is the same either way — the badge is there so you know which rule produced it.
The text stays in this browser. It is kept in localStorage so a reload does not lose a draft, and Clear removes it; there is no account, no upload and no server copy. That also means the draft is gone if you clear site data, use a private window or switch browsers.