Engineering

Character limits for SMS and social posts

A counter is only honest if it counts the unit the platform counts. Emoji, accents and SMS encoding change the number, so count graphemes, show the segments, and enforce on the server.

6 min read

A form field that says “280 characters left” is only as honest as the count behind it. A writer types an emoji, a family of people, or an accented letter, and the counter disagrees with what the platform accepts. The count can be too high, so the writer is blocked too early, or too low, so the post is rejected at the last step. Both come from counting the wrong unit.

Count what the platform counts

Different limits use different units. A plain string.length counts UTF-16 code units, which is what JavaScript stores. A user-visible character is a grapheme cluster, which may be several code units long. Some platforms count code points, some count bytes in a given encoding, and some apply their own rules. Find out which unit the destination uses, and count in that unit. A counter that is wrong for the destination is worse than no counter.

Code example
const text = "e\u0301\u{1F468}\u200D\u{1F469}"; // accented e, then a family emoji

text.length;                                     // 6: UTF-16 code units
[...text].length;                                // 4: code points
[...new Intl.Segmenter().segment(text)].length;  // 2: user-perceived characters

The three numbers differ for the same text. A writer sees two characters. A system that counts code points sees four, and one that counts UTF-16 units sees six. Pick the unit the destination uses, and name it in the counter’s help text, so a writer who gets a rejection knows why.

SMS adds a second limit: encoding

A plain text SMS can hold 160 characters from the GSM alphabet. One character outside that set, such as a curly quote or an emoji, switches the whole message to UCS-2 encoding, which holds 70 characters. The counter must show the switch, because the same message can grow from one segment to three when a writer adds a single emoji. Show the segment count and the encoding, and update both as the writer types.

Count on input, enforce on submit

A hard limit that stops typing at the boundary feels punitive, and it breaks paste. A writer who pastes a paragraph into a 280-character field sees the paste cut off mid-word. Prefer a counter that turns red past the limit, and enforce the limit when the post is submitted. If text must be cut, cut at a grapheme boundary, never inside an emoji sequence, which would leave a broken symbol behind.

The server counts too, with the same rules

A client-side counter is advice, not validation. The server must check the length with the same unit the destination uses, because a request can come from anywhere. Keep the rule in one function that both the counter and the server call. A mismatch between the two is the bug where the counter says the post is fine and the server returns an error with no explanation.

A short test list


RichTextEditor is a perpetual-licence JavaScript editor — one purchase, self-hosted, no metered editor loads. Download the evaluation or see how it compares.