Text Counter

Count characters, words, sentences, and more

0
Characters
0
Characters (no spaces)
0
Words
0
Sentences
0
Paragraphs
0
Lines

Code Units, Code Points, Graphemes: Three Different Lengths for One String

Ask "how long is this string?" and JavaScript, Unicode, and the user each give a different number. String.prototype.length counts UTF-16 code units — the 16-bit chunks of JavaScript's internal encoding. Every character outside the Basic Multilingual Plane is stored as a surrogate pair, so '👍'.length is 2 even though you see one symbol. Spreading a string ([...s]) iterates code points instead, which fixes surrogate pairs but not sequences. The family emoji 👨‍👩‍👧‍👦 is four person code points stitched together with three zero-width joiners (U+200D): length 11, code point count 7, yet one visible glyph. A country flag is two regional indicator symbols — 🇰🇷 has length 4. Skin-tone modifiers stack yet another code point onto the base emoji. What users perceive is the grapheme cluster, and since 2021 the platform can count it natively: Intl.Segmenter with granularity 'grapheme' segments a string exactly the way a text cursor moves through it. That is the number a character counter should show as "characters the user sees". Backends add a fourth ruler. A MySQL utf8mb4 VARCHAR(10) counts code points, and so does a Postgres varchar(10); but APIs that limit by bytes see each CJK character as 3 UTF-8 bytes and each emoji as 4. The same Japanese string can be simultaneously under the limit in a JS length check and over the limit in a byte-counting backend. Always validate with the same ruler the enforcing system uses.
const fam = '👨‍👩‍👧‍👦';
fam.length                      // 11 UTF-16 code units
[...fam].length                 // 7 code points (4 people + 3 ZWJ)
const seg = new Intl.Segmenter('en', { granularity: 'grapheme' });
[...seg.segment(fam)].length    // 1 — what the user sees

'👍'.length                     // 2 — surrogate pair
'🇰🇷'.length                     // 4 — two regional indicators

Platform Limits Decoded: Twitter's 280, SMS's 160, Google's 600 Pixels

Real-world limits are rarely raw character counts. Twitter/X allows 280 weighted units: most Latin letters, digits, and punctuation weigh 1, but CJK characters, emoji, and other wide ranges weigh 2 — so a Japanese tweet maxes out at 140 characters. Every URL is billed at exactly 23 units regardless of its actual length, because t.co wrapping normalizes it. SMS is stranger. A single message holds 160 characters if the whole text fits the GSM-7 alphabet. One character outside it — an emoji, a curly apostrophe pasted from a word processor, certain accented letters — silently switches the entire message to UCS-2 and the budget drops to 70. Long messages are split into concatenated segments that each sacrifice header bytes, leaving 153 (GSM-7) or 67 (UCS-2) characters per segment. A single smart quote can turn one billed message into three; bulk-SMS invoices reveal this constantly. Elsewhere: Instagram captions cap at 2,200 characters and 30 hashtags. Google truncates title tags by rendered pixel width — about 600px, which fits roughly 50–60 characters depending on letter widths (WWW is far wider than iii) — and meta descriptions around 155–160 characters. The practical takeaway: know whether your target counts code units, weighted units, GSM-7 septets, or pixels, and test with representative text rather than lorem ipsum.

Word Counting Is Language-Dependent — CJK Breaks the Whitespace Rule

Splitting on whitespace is a solid word counter for English, German, or Spanish. It returns exactly 1 for any Chinese or Japanese sentence, because those scripts are written without spaces. Real word segmentation for CJK needs a dictionary or a statistical model: MeCab is the standard morphological analyzer for Japanese, jieba for Chinese, and in the browser Intl.Segmenter with granularity 'word' exposes ICU's dictionary-based segmentation with zero dependencies. Korean sits in between. It does use spaces, but between eojeol — word-plus-particle units — so whitespace counting yields eojeol, not dictionary words. In practice Korean writing is measured in characters anyway, a convention inherited from the 200-character manuscript grid (wonogoji) still referenced in schools and publishing. The distinction has money attached. Professional translation is billed per source word for English-like languages but per character for Japanese and Chinese, and agencies apply published conversion factors — on the order of two CJK characters per English word — when quoting across the boundary. Academic and legal limits split the same way: a Japanese journal specifies a character count, an English one specifies words. If your product shows a word count to users in multiple languages, count isWordLike segments from Intl.Segmenter; the same code path produces linguistically sane numbers for English and CJK alike.
function countWords(text, locale = 'en') {
  const seg = new Intl.Segmenter(locale, { granularity: 'word' });
  return [...seg.segment(text)].filter(s => s.isWordLike).length;
}

countWords('The quick brown fox');            // 4
countWords('日本語は分かち書きをしない', 'ja');  // dictionary-segmented
'日本語は分かち書きをしない'.split(/\s+/).length; // 1 — naive split fails

NFC vs NFD: Normalization Changes the Count Without Changing the Pixels

Unicode frequently has more than one encoding for the same visible text. The Korean syllable 한 exists as one precomposed code point (U+D55C) or as three conjoining jamo (initial ᄒ, vowel ᅡ, final ᆫ); é is either the single code point U+00E9 or an e followed by combining acute U+0301. NFC is the composed form, NFD the decomposed one. They render pixel-identically and compare unequal. On a Mac this is not theoretical. HFS+ stored filenames in a decomposed variant, and several macOS surfaces still hand you NFD text — copy a Korean filename from Finder, paste it into a counter, and 한글.txt reports 10 characters instead of 6. The same applies to filenames uploaded from macOS: a backend maxlength check can pass for typed text and fail for pasted text that looks identical. The second symptom is equality: 'é' === 'é' can be false when one side is NFC and the other NFD, which quietly breaks deduplication, search matching, and unique constraints. The fix is one line — call str.normalize('NFC') before counting, comparing, or hashing; normalization is idempotent and fast. Grapheme-cluster counting via Intl.Segmenter is largely immune, since both encodings of 한 form a single cluster — one more argument for counting graphemes whenever the number is shown to a human.
const nfc = '한'.normalize('NFC');  // U+D55C
const nfd = '한'.normalize('NFD');  // U+1112 U+1161 U+11AB
nfc.length              // 1
nfd.length              // 3 — same pixels, three code units
nfc === nfd             // false

'한글.txt'.normalize('NFD').length  // 10
'한글.txt'.normalize('NFC').length  // 6

Reading-Time Math: 238 Words per Minute and Other Defensible Numbers

Reading-time estimates look like guesswork, but there are measured anchors. Brysbaert's 2019 meta-analysis of nearly 190 studies puts silent reading of English non-fiction at about 238 words per minute, with fiction slightly faster around 260. Medium's read-time algorithm uses 265 wpm and adds 12 seconds for the first image, decreasing one second per subsequent image down to a floor of 3. For Japanese and Chinese, planning figures are quoted in characters instead — roughly 400 to 600 characters per minute for comfortable silent reading. Average word length makes conversions possible. English runs about 4.7 letters per word, roughly 5.7 characters including the following space, so a 1,000-word article is near 5,700 characters and reads in a bit over four minutes at 238 wpm. The same arithmetic translates limits: a 155-character meta description holds about 25 English words; a 160-character GSM-7 SMS about 28. Spoken delivery is far slower than silent reading — narration and conference talks sit around 130 to 150 wpm — which is why a five-minute read becomes a nine-minute voiceover script. Density is a useful editing signal too: if your average word length drifts well above six letters, the text is jargon-heavy and will read slower than any per-word formula predicts.
Last updated:

About this tool

A character and word counter analyses any text and reports characters (with and without spaces), words, sentences, paragraphs, and lines in real time. It is useful for content with strict length limits — Tweets, meta descriptions, app store listings — and for tracking writing progress on essays, articles, and translations where word count is the deliverable.

How to use

  1. Paste or type your text into the input box.
  2. All six metrics update on every keystroke — no button needed.
  3. Use the characters-without-spaces value when comparing against APIs that count code points.
  4. Watch the words count for blog posts, essays, and content with a target length.
  5. Use line count when working with logs, source code, or CSV-like data.

Common use cases

  • Checking that an SEO meta description stays under 160 characters.
  • Hitting a 280-character limit for a Tweet or 300-character limit for a Bluesky post.
  • Tracking word count for academic essays or magazine assignments.
  • Estimating reading time (~200 words per minute) for a blog post.
  • Auditing log file size by line count when piping output into the textarea.
  • Validating user-generated content against backend length constraints before submitting.

Frequently asked questions

Q. How are words counted?

A. By splitting on whitespace runs after trimming. So "hello world" = 2 words, but "hello world" (multiple spaces) is also 2.

Q. Why is character count different from what Twitter shows?

A. Twitter weights certain characters (like emoji and CJK) differently. This tool reports raw JavaScript string length (UTF-16 code units), which matches most backend validations.

Q. How are sentences detected?

A. By counting end-of-sentence punctuation (. ! ?). Abbreviations like "Mr." may inflate the count slightly.

Q. Does the tool send my text anywhere?

A. No. Counting happens entirely in your browser; nothing is uploaded.