DEV Community

Joe Lin for BeGoodTool.com

Posted on

Multi-Platform Text Length Previewer: Why SMS Character Counts Change

I built the Multi-Platform Text Length Previewer for the moment when a sentence looks fine in one editor but gets clipped everywhere else. A social post, a search snippet, and an SMS campaign do not share one universal definition of “character.” The useful part is not memorizing a collection of limits; it is seeing which counting rule is being applied before the copy is published.

The primary reader here is a content developer or frontend engineer preparing the same message for several channels. The problem is easy to describe: a JavaScript .length check says the text fits, but a phone message becomes two SMS segments, or a search description displays only its first line. The implementation shows why those outcomes are different, and the previewer is a working example rather than a replacement for checking the final platform.

A platform card is a display rule, not a universal validator

The tool keeps its visible platform rules in one small array:

const PLATFORMS = [
  { id: "twitterPost",     icon: "𝕏", labelKey: "twitterLabel",    limit: 280 },
  { id: "googleTitle",     icon: "🔍", labelKey: "googleTitleLabel", limit: 60 },
  { id: "googleDesc",      icon: "🔍", labelKey: "googleDescLabel",  limit: 155 },
  { id: "youtubeTitle",    icon: "",  labelKey: "youtubeLabel",     limit: 60 },
  { id: "linkedinPost",    icon: "in", labelKey: "linkedinLabel",    limit: 180 },
  { id: "facebookPost",    icon: "f",  labelKey: "facebookLabel",    limit: 477 },
  { id: "instagramCaption",icon: "📸", labelKey: "instagramLabel",   limit: 125 },
];
Enter fullscreen mode Exit fullscreen mode

Those numbers are used for a practical display preview. Each card reports used, calculates a percentage bar, and calls truncate to split the visible and overflow text:

function truncate(text, limit) {
  return text.length <= limit ? text : text.slice(0, limit);
}

function statusClass(platform) {
  const ratio = platform.used / platform.limit;
  if (ratio <= 0.85) return "status-ok";
  if (ratio <= 1.0) return "status-warn";
  return "status-over";
}
Enter fullscreen mode Exit fullscreen mode

The 85% warning is deliberately separate from “over.” It gives a writer a signal before the hard cutoff, while still showing exactly what the code considers beyond the configured limit. The source also describes Google’s values as visible search cutoffs rather than storage limits. That distinction matters: a title can be stored successfully and still be shortened in a search result.

SMS is a different counting problem

SMS is where a plain character counter becomes misleading. The implementation first checks every code point against two sets: GSM7_BASIC and GSM7_EXT. An extended GSM character such as , {, or | consumes two character units:

function calcGsmLength(text) {
  let len = 0;
  for (const ch of text) {
    if (GSM7_EXT.has(ch)) len += 2;
    else len += 1;
  }
  return len;
}

function isGsm7(text) {
  for (const ch of text) {
    if (!GSM7_BASIC.has(ch) && !GSM7_EXT.has(ch)) return false;
  }
  return true;
}
Enter fullscreen mode Exit fullscreen mode

If every character is in the GSM sets, one segment has capacity 160 and multipart messages use 153 units per segment. Otherwise the tool switches to Unicode counting: 70 units for one segment and 67 per multipart segment. It uses [...text].length for that Unicode count, which iterates by code point instead of splitting UTF-16 surrogate pairs. The result is then computed with Math.ceil(gsmLen / multiSegSize) after the first segment threshold is crossed.

This is why adding one character can change more than the “characters used” number. A curly brace costs two GSM units, while a single CJK character switches the whole message to the smaller Unicode capacity. The interface also builds visual segment cards, so the writer can inspect the actual chunks rather than only seeing a total.

The UI updates from one reactive source

There is no separate “run” button for platform previews. The textarea is bound to inputText, and a computed value maps the same string across all platform definitions:

const platformPreviews = computed(() =>
  PLATFORMS.map(p => ({
    ...p,
    name: platformName(p.id),
    used: inputText.value.length,
  }))
);
Enter fullscreen mode Exit fullscreen mode

That makes the preview easy to reason about: one edit produces one new array of cards, and the SMS panel independently derives its encoding and segment data from the same inputText. The visible overflow is styled rather than silently discarded, which is useful when a sentence’s important qualifier is the part that falls outside a limit.

There is also a subtle mismatch worth noticing. The platform cards use JavaScript text.length and slice, while SMS uses code-point iteration or GSM septet units. Emojis, some symbols, and other non-BMP characters can therefore occupy two UTF-16 code units in a platform card even though the SMS branch counts them as one Unicode code point. That is not an accidental inconsistency; it reflects different approximations, but it means a card’s “characters” number should not be treated as an SMS billable-unit count.

Limits and the final check

The limits are maintained as product data, not fetched from each platform. Search engines, social networks, fonts, languages, and devices can change how much text is displayed. The source explicitly calls its truncation approximate. text.slice(limit) also does not understand words, grapheme clusters, or platform-specific link previews, so a cutoff can occur in the middle of a word or a visually combined emoji sequence.

The SMS visual splitter is described as an approximation too. For GSM it accounts for extended characters while filling each 153-unit chunk; for Unicode it slices an array of code points. It cannot model carrier concatenation details or every renderer’s treatment of line breaks. I use the output to catch obvious surprises, then paste the final copy into the actual publishing surface.

I turned this implementation into a small free tool: Multi-Platform Text Length Previewer.

Top comments (0)