What this Word Counter measures
Word Counter runs analyzeText on the current editor string and shows the fields that helper returns: words, characters (text.length), charactersNoSpaces, sentences, paragraphs, lines, avgWordsPerSentence, avgCharsPerWord, longestWord, plus readingTime and speakingTime labels. Length goals from WORD_GOALS compare the current character or word total to a fixed limit. There is no keyword-density table, letter-frequency chart, readability grade, grammar panel, plagiarism check, or AI-detection score.
Input is typed or pasted text, or a locally read plain .txt file. A displayed count is a regex/string-length result. It does not prove that text meets a platform’s submission rule, contains correct grammar, is original, is readable for every audience, or has been accepted by a third-party service.
How words, characters, sentences, paragraphs, and lines are counted
- Words
- trim, then split on /\s+/, then drop empty pieces. Not Intl.Segmenter. Punctuation stays attached (hello, is one token).
- Characters
- JavaScript string length (UTF-16 code units), including spaces, tabs, and newlines.
- No spaces
- length after replace(/\s/g, "") — all whitespace, not only ordinary spaces.
- Sentences
- split on /[.!?]+/, trim, drop empties. Abbreviations, decimals, and URLs can inflate the total.
- Paragraphs
- non-empty blocks split on /\n\s*\n/ (blank lines). A single newline does not start a new paragraph.
- Lines
- if trim is empty, 0; else split on /\n/ and count rows with trim().length > 0. Blank lines are omitted.
Averages round to one decimal: words/sentences and charactersNoSpaces/words. longestWord is the whitespace token with the greatest .length (UTF-16), or an em dash when there are no words. HTML, Markdown, and JSON entered in the box are counted as literal characters and tokens; tags are not stripped.
Reading/speaking-time estimates, if implemented
estimateDuration(wordCount, wordsPerMinute) divides the word total by a constant. READING_WPM is 200; SPEAKING_WPM is 130. The UI does not expose those rates. Zero words → "0 min". Below one minute, the label is whole seconds with a floor of 1 second when there is at least one word. At one minute or more, the label is Math.ceil(minutes) plus " min". The helper also stores readingMinutes/speakingMinutes as the unrounded ratio; the page displays the labels only.
These figures do not model difficulty, pauses, language, or a specific reader. They are planning estimates from the same whitespace word count, not timed measurements.
Whitespace, punctuation, emojis, and multilingual text
Repeated spaces, tabs, and newlines collapse for word splitting. Hyphenated forms (well-known) and apostrophes (don't) stay one token because no whitespace splits them. A URL or number without spaces is one word. Emoji and other non-spacing symbols can be their own token if surrounded by whitespace; character totals still use UTF-16 length, so many emoji and surrogate pairs add more than one to characters.
Scripts written without spaces (typical Chinese, Japanese, or Korean runs) often become a single word token. That is expected for this splitter, not linguistic word segmentation. Newlines separate words and lines; only a blank line separates paragraphs.
How to use the real controls
- Idle heading is Paste or type your text. Click Focus editor to focus the textarea, or type/paste (placeholder “Paste an essay, blog draft, caption, or script here…”). The idle onChange only calls onText when nextValue.trim() is truthy, so whitespace-only stays idle. There is no Count button.
- Click Upload .txt or drop a file onto the idle card. accept is .txt,text/plain. validateTextFile rejects over 5 MB and non-plain types (empty MIME is allowed if the name ends with .txt or type is text/plain or ""). A valid file replaces the editor via readTextFile.
- Once hasText is true, Live editor updates analyzeText on every keystroke (placeholder “Paste or type your text here…”). The heading is Your text, or the imported fileName.
- Click a Length goals chip: Tweet (280 characters), SEO meta (160 characters), Essay (500 words), Blog post (1000 words), or Speech (750 words). getGoalProgress marks over / near (≥90%) / ok. Switching chips does not change the text.
- On the editor, click Copy text (Copied for 1.5 seconds; disabled while empty), Copy stats (Stats copied; disabled while whitespace-only), or Clear. In the sidebar, click Copy stats summary, Copy text (Text copied), or Start over. Copy stats / Copy stats summary write formatStatsSummary.
- Clear and Start over empty text, filename, file error, and copy flags. There is no undo, history, sample document, or download.
What the counters do not validate
- Grammar, spelling, tone, or “writing quality.”
- Originality, plagiarism, citations, or AI-generated-text detection.
- Readability formulas (Flesch, grade level) — avgWordsPerSentence is only wordCount/sentenceCount.
- SEO ranking, keyword density, or meta-description performance — the 160-character goal is a local bar only.
- That a school, publisher, or CMS will accept the draft.
longestWord is the longest whitespace token, not a dictionary headword. Goal bars are arithmetic against WORD_GOALS, not a submission API.
Checking an external platform’s limit separately
Tweet 280 and SEO meta 160 use this page’s JavaScript character length, not Twitter’s weighted/grapheme counting or Google’s live snippet rules. Essay 500, blog 1000, and speech 750 are fixed word targets in WORD_GOALS, not a particular university or CMS quota. If a form, social network, or assignment uses a different tokenizer, grapheme count, or exclude-spaces rule, check that product’s counter. This tool does not fetch those limits and does not mark text as accepted.
Privacy and browser-processing boundaries
analyzeText and readTextFile (file.text()) run in the browser. Typed, pasted, and imported plain text is not POSTed to a counting API. FileWorkflowShell uses processingMode local and the privacy note “Private by design · Text stays in your browser · No upload.” The idle footer repeats “Private · Local · No upload.” Identified analytics still exist: paste may fire tool_started with method paste; handleSetText fires tool_started with method paste/file/input; successful .txt import fires tool_upload_added with fileType and fileSize; first success fires tool_success with wordCount and characterCount. Identified events do not include the raw text, paste body, or filename. Site assets, ads, clipboard permission, extensions, and shared devices remain outside this helper.
A short text-review checklist
- Confirm you are measuring the string in this editor (paste vs a .txt that replaced it), not a Word/Docs window with a different tokenizer.
- If sentences look high, scan for Dr., e.g., 3.14, and URLs — split is /[.!?]+/ only.
- If CJK or emoji totals disagree with another app, remember whitespace words + UTF-16 length, not grapheme clusters or Intl.Segmenter.
- Treat reading/speaking labels as 200/130 wpm estimates, not a timed read-aloud.
- Treat Tweet/meta/essay bars as local WORD_GOALS, then confirm the real platform or assignment rule.
- Copy stats if you need a snapshot; Clear deletes the draft with no undo.
When another DevToolKit tool is more appropriate
- Text Diff — when you need to compare two versions. This page counts a single string and does not highlight insertions or deletions.
- Regex Tester — when you need to find or test a pattern. This counter only splits on whitespace and . ! ?; it does not search or replace.