Character Counter: Count Characters, Words & Sentences Online
Key Takeaways
- ▸Twitter/X standard accounts are limited to 280 characters per post — links are always shortened to 23 characters regardless of URL length, per X's 2026 platform specifications.
- ▸Google truncates meta descriptions beyond 155–160 characters on desktop and ~120 on some mobile layouts — always write your CTA within the first 155 characters.
- ▸SMS segments drop from 160 to 70 characters the moment a single non-GSM-7 character appears — one emoji converts the entire message to UCS-2 encoding.
- ▸JavaScript's
.lengthcounts UTF-16 code units, not visible characters — the family emoji 👨👩👧👦 reports.length === 11. UseIntl.Segmenterfor accurate UI counters. - ▸Per Grammarly's writing research, character count is among the most-used text metrics globally — driven by social media character limits, SEO constraints, and database column validation.
The Tweet That Got Cut Off
Picture this: you schedule a product announcement tweet at 9 PM and wake up to find Twitter truncated your carefully crafted call to action with an ellipsis. The link — buried in the truncated portion — never got clicked. This happens because the author counted characters in a plain text editor that does not emulate Twitter's counting logic, and the numbers disagreed by exactly 4 characters.
Character limits are load-bearing constraints in a developer's world. They govern form validation, database column sizes, SMS gateway costs, SEO snippet rendering, notification payloads, and social media publishing. Getting the count wrong has real consequences — from truncated copy to UCS-2 SMS charges that cost double what you budgeted.
BytePane's character and word counter runs entirely in your browser — paste any text and get an instant breakdown of characters (with and without spaces), words, sentences, lines, and reading time. No data sent to any server. This guide goes deeper: why platform limits differ, how encoding affects the count, and the exact JavaScript code you need for production character counters.
Platform Character Limits: The Definitive 2026 Reference
Different platforms apply character limits for different reasons — UX, storage, API design, legacy protocol constraints. Here is a comprehensive reference based on official 2026 platform specifications:
| Platform / Field | Limit | Encoding | Notes |
|---|---|---|---|
| X (Twitter) — Post | 280 chars | Unicode | Links → 23 chars. X Premium: 25,000 |
| X — Bio | 160 chars | Unicode | Display name: 50 chars |
| LinkedIn — Post | 3,000 chars | Unicode | Connection request: 300. InMail: 2,000 |
| Instagram — Caption | 2,200 chars | Unicode | Only first 125 shown before "more" |
| Google — Title Tag | ~50–60 chars | Unicode | Pixel-based: ~600px. Chars vary by font. |
| Google — Meta Description | 155–160 chars | Unicode | Mobile may truncate at ~120 |
| SMS — GSM-7 | 160 chars/segment | GSM-7 | Multi-part: 153 chars per segment |
| SMS — UCS-2 (emoji) | 70 chars/segment | UCS-2 | Multi-part: 67 chars per segment |
| WhatsApp — Message | 65,536 chars | UTF-8 | Status (Stories): 700 chars |
| TikTok — Caption | 2,200 chars | Unicode | Bio: 80 chars |
The most important nuance in this table: Twitter counts characters, not bytes. A single Chinese character like 文 counts as 1 character toward the 280 limit, not 3 bytes (UTF-8) or 2 bytes (UCS-2). This is deliberate — Unicode characters are first-class in Twitter's counting model. SMS, by contrast, uses byte-based encoding where character limits differ by encoding scheme.
The SMS Encoding Trap: Why One Emoji Doubles Your Cost
SMS is the character counting domain with the most financial consequences, and the most confusion. The root cause is encoding: SMS was designed in the 1980s around GSM-7 — a 7-bit encoding with 128 characters covering basic Latin, numbers, and a few special symbols. A GSM-7 message fits 160 characters in a single 140-byte segment.
The problem: GSM-7 does not include emoji, curly quotes (" "), em dashes (—), or most non-Latin characters. The moment your message contains any character outside GSM-7, the SMS gateway switches the entire message to UCS-2 (a 16-bit encoding). The per-segment capacity immediately drops from 160 to 70 characters.
// GSM-7 character set (simplified)
const GSM7_CHARS = /^[A-Za-z0-9 \r\n@£$¥èéùìòÇØøÅåΔ_ΦΓΛΩΠΨΣΘΞÆæßÉ!"#%&'()*+,\-./:;<=>?¡ÄÖÑܧ¿äöñüà^{}\\\[~\]|€]*$/
function smsInfo(text) {
const isGSM7 = GSM7_CHARS.test(text)
const charCount = text.length
if (isGSM7) {
const segments = charCount <= 160 ? 1 : Math.ceil(charCount / 153)
return { encoding: 'GSM-7', chars: charCount, segmentSize: 160, segments }
} else {
// UCS-2: emoji, curly quotes, em dash, etc.
const ucsCount = [...text].length // Unicode code points
const segments = ucsCount <= 70 ? 1 : Math.ceil(ucsCount / 67)
return { encoding: 'UCS-2', chars: ucsCount, segmentSize: 70, segments }
}
}
smsInfo("Hello, world! Great news.")
// { encoding: 'GSM-7', chars: 25, segmentSize: 160, segments: 1 }
smsInfo("Hello! 🎉 Great news.")
// { encoding: 'UCS-2', chars: 21, segmentSize: 70, segments: 1 }
// NOTE: 21 chars costs the same as 70 — you "waste" 49 characters of capacity
smsInfo("A".repeat(155))
// { encoding: 'GSM-7', chars: 155, segmentSize: 160, segments: 1 }
smsInfo("A".repeat(161))
// { encoding: 'GSM-7', chars: 161, segmentSize: 160, segments: 2 }
// Now 2 segments charged — multi-part header uses 7 chars, leaving 153 per segmentThe financial implication: a 2-segment GSM-7 message costs 2× a 1-segment message. Add an emoji and you go from 160 chars/segment to 70 chars/segment — meaning a 120-character message that would be 1 GSM-7 segment becomes a 2-segment UCS-2 message when you add a single emoji. This is a well-known pitfall in SMS marketing. Per Twilio's developer documentation, unicode characters are one of the most common causes of unexpectedly high SMS costs.
GSM-7 Extended Characters: The GSM-7 extended table includes €, {, }, [, ], ~, |, and \. These are still GSM-7 but count as 2 characters each — an extended table escape byte plus the character. A message with just one € sign effectively uses 2 character slots.
Building a Production Character Counter in JavaScript
The apparent simplicity of character counting in JavaScript is a trap. The String.prototype.length property returns UTF-16 code units, not Unicode characters, not grapheme clusters (visible characters). For an English-only form field, it does not matter. For any UI that users can populate with emoji or international text, it matters enormously.
const text = "Hello 👨👩👧👦 World"
// 1. UTF-16 code units — what .length returns
// Use for: database varchar limits (which are often byte/unit based)
text.length // 18 (family emoji = 11 UTF-16 units)
// 2. Unicode code points — what most humans mean by "characters"
// Use for: Twitter counting, simple display counters
[...text].length // 9
Array.from(text).length // 9
// 3. Grapheme clusters — what users see as individual characters
// Use for: accurate UI character counters, text editors
const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' })
[...segmenter.segment(text)].length // 9 (family emoji = 1 visible character)
// Practical: character count without spaces (some platforms use this)
function charsWithoutSpaces(str) {
return Array.from(str.replace(/\s/g, '')).length
}
// Practical: Twitter-style counter (code points, links = 23)
function twitterCount(text) {
// Replace URLs with 23-char placeholder
const normalized = text.replace(/https?:\/\/\S+/g, 'x'.repeat(23))
return Array.from(normalized).length
}Real-Time Counter with React
Here is a production-quality character counter component that correctly handles emoji, shows color-coded limits, and updates in real time:
import { useState, useMemo } from 'react'
const MAX_CHARS = 280 // Twitter limit
function CharacterCounter() {
const [text, setText] = useState('')
const stats = useMemo(() => {
const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' })
const graphemes = [...segmenter.segment(text)]
const charCount = graphemes.length
const remaining = MAX_CHARS - charCount
const percentage = (charCount / MAX_CHARS) * 100
return {
chars: charCount,
words: text.trim() ? text.trim().split(/\s+/).filter(Boolean).length : 0,
sentences: text.trim() ? (text.match(/[.!?]["')]*(?:\s|$)/g) || [text]).length : 0,
remaining,
percentage,
status: remaining < 0 ? 'over' : remaining < 20 ? 'warning' : 'ok',
}
}, [text])
const statusColor = {
ok: 'text-green-400',
warning: 'text-yellow-400',
over: 'text-red-400',
}[stats.status]
return (
<div className="space-y-3">
<textarea
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Start typing..."
rows={4}
className="w-full p-3 bg-dark-surface border border-dark-border rounded-lg resize-none"
/>
<div className="flex justify-between text-sm">
<span className="text-text-muted">
{stats.words} words · {stats.sentences} sentences
</span>
<span className={statusColor}>
{stats.remaining >= 0
? `${stats.chars}/${MAX_CHARS}`
: `${Math.abs(stats.remaining)} over limit`}
</span>
</div>
{/* Progress bar */}
<div className="h-1 bg-dark-border rounded-full overflow-hidden">
<div
className={`h-full transition-all ${stats.status === 'over' ? 'bg-red-400' : stats.status === 'warning' ? 'bg-yellow-400' : 'bg-green-400'}`}
style={{ width: `${Math.min(stats.percentage, 100)}%` }}
/>
</div>
</div>
)
}The Intl.Segmenter API is available in all modern browsers (Chrome 87+, Firefox 78+, Safari 14.1+, Edge 87+) and Node.js 16+. For older environments, the graphemer npm package provides polyfill behavior based on Unicode Annex 29 segmentation rules.
SEO Character Limits: Title Tags and Meta Descriptions
Google does not publish a character limit for title tags — it publishes a pixel width limit of approximately 600 pixels for desktop and 920 pixels for mobile. The character count that stays within the pixel budget varies by font metrics. In practice, Moz's analysis of 89,000 SERPs found that 50–60 characters keeps 95%+ of titles within the rendered threshold using typical Latin glyphs.
Meta descriptions are different: Google caps the snippet at approximately 920 pixels width on desktop, which translates to roughly 155–160 characters. Per research by Portent (analyzing 35,000 search result snippets), descriptions between 150 and 158 characters had the lowest truncation rate. Descriptions over 160 characters were truncated in 97% of cases.
| SEO Element | Safe Range | Maximum Before Truncation | Pixel Budget |
|---|---|---|---|
| Page title tag | 50–60 chars | ~70 chars (wide glyphs sooner) | ~600px desktop |
| Meta description (desktop) | 150–158 chars | 160 chars | ~920px |
| Meta description (mobile) | 120–125 chars | ~130 chars | Varies by device |
| Open Graph title | 40–60 chars | Platform-dependent | N/A (varies by sharer) |
A practical rule: write your meta description CTA within the first 130 characters. The remaining 25–30 characters of budget buffer against mobile truncation. If you are writing titles with brand names (“Tool Name | Brand”), count that suffix against your 60-character budget — Google sometimes rewrites titles that exceed the limit anyway.
For programmatic SEO pages where descriptions are generated from templates, building character count validation into your content pipeline prevents truncated snippets at scale. Check our regex cheat sheet for patterns you can use to validate meta description length in a CI pipeline.
Database Column Limits and Encoding Gotchas
When developers think about character limits in code, they usually think about UI validation. But the deepest consequences of character counting errors surface in database persistence. The difference between a VARCHAR(255) and a character count depends entirely on the database's charset configuration:
-- MySQL utf8mb4 (correct Unicode support) -- VARCHAR(255) stores 255 CHARACTERS, up to 4 bytes each = up to 1020 bytes -- A single emoji (4-byte UTF-8) counts as 1 character toward the 255 limit CREATE TABLE posts (content VARCHAR(280) CHARACTER SET utf8mb4); -- MySQL utf8 (legacy — only 3-byte Unicode, emoji breaks!) -- The 💩 emoji (U+1F4A9) is 4 bytes → INSERT fails silently or errors -- ALWAYS use utf8mb4, not utf8, in MySQL CREATE TABLE posts_wrong (content VARCHAR(280) CHARACTER SET utf8); -- AVOID -- PostgreSQL VARCHAR(n) — n is CHARACTER count, not byte count -- Both emoji and CJK chars count as 1 character toward the limit CREATE TABLE posts (content VARCHAR(280)); -- Always correct for Unicode -- PostgreSQL TEXT — no limit, always UTF-8 CREATE TABLE posts (content TEXT); -- Checking actual storage in Postgres SELECT char_length(content), octet_length(content) FROM posts WHERE id = 1; -- char_length: Unicode character count (what users see) -- octet_length: byte count (what the disk stores, 1–4 bytes per char)
The MySQL utf8 charset — distinct from utf8mb4 — only supports Unicode characters up to U+FFFF (3 bytes). This means emoji (which start at U+1F000) are silently truncated or cause errors. The MySQL documentation has acknowledged this as a longstanding design limitation. Per the MySQL 8.0 docs, “Applications that need supplementary character support should use utf8mb4.” The wrong charset is responsible for a significant portion of the “data truncated” errors reported on Stack Overflow MySQL questions.
Character Density and Readability Metrics
Beyond raw counts, character and word data feeds into readability metrics used by content teams, editors, and automated SEO tools. The most commonly referenced is the Flesch Reading Ease score, calculated from syllables, words, and sentences:
// Flesch Reading Ease: 0–100, higher = easier to read
// Formula: 206.835 - 1.015 * (words/sentences) - 84.6 * (syllables/words)
function fleschReadingEase(text) {
const words = text.trim().split(/\s+/).filter(Boolean).length
const sentences = (text.match(/[.!?]+/g) || []).length || 1
const syllables = text
.toLowerCase()
.split(/\s+/)
.reduce((total, word) => {
// Approximate syllable count: vowel groups, subtract trailing 'e'
const syllableCount = word
.replace(/[^aeiou]/gi, '')
.replace(/e$/, '')
.length || 1
return total + syllableCount
}, 0)
const score = 206.835 - 1.015 * (words / sentences) - 84.6 * (syllables / words)
return Math.max(0, Math.min(100, Math.round(score)))
}
// Score interpretation:
// 90–100: Very easy (5th grade)
// 70–80: Easy (7th grade)
// 60–70: Standard (8th–9th grade)
// 50–60: Fairly difficult (10th–12th grade)
// 30–50: Difficult (college level)
// 0–30: Very confusing (professional/academic)According to the Nielsen Norman Group's research on web readability (analyzing 79 usability studies), web users read only 20–28% of text on a page. Short sentences (15–20 words average) and high Flesch scores significantly increase comprehension in web contexts. The U.S. federal government mandates a minimum 60 Flesch score for public-facing documents under the Plain Writing Act of 2010.
For developers building CMS systems or writing assistants, the combination of character count, word count, sentence count, and Flesch score provides a complete real-time writing quality dashboard. Many modern editors (Hemingway Editor, Grammarly, Notion AI) surface exactly these four metrics in their UIs.
If you need to analyze large blocks of text programmatically, our word counter deep-dive covers the algorithmic edge cases — CJK text, emoji Unicode handling, and word-boundary detection for production systems.
Character Counter Tools: What to Look For
The market for online character counting tools ranges from single-purpose counters to full text analysis suites. Here is what separates adequate tools from genuinely useful ones, based on what actually matters in developer and content workflows:
| Feature | Why It Matters | BytePane | WordCounter.net | CharacterCountOnline |
|---|---|---|---|---|
| Client-side processing | Text never sent to server — privacy | Yes | Partial | Yes |
| Chars with/without spaces | Academic and ad copy requirements | Yes | Yes | Yes |
| Sentence count | Reading ease calculations | Yes | Yes | No |
| Reading time estimate | Blog/content planning | Yes | Yes | No |
| No account required | Quick one-off counts | Yes | Yes | Yes |
| No ads interrupting count | Focus when working under deadline | Yes | No | No |
One critical feature this table does not capture: how a tool handles emoji and non-ASCII input. Many older character counters use JavaScript's .length property, which will report the family emoji 👨👩👧👦 as 11 characters. If you are counting for a Twitter field that treats it as 2 (code points), this discrepancy matters. Test any tool you plan to rely on with input like 👩💻🏳️🌈 and verify the count matches your platform's actual behavior.
Frequently Asked Questions
What is the character limit for Twitter/X in 2026?
Standard X accounts are limited to 280 characters per tweet. Links are shortened to 23 characters regardless of actual URL length. X Premium subscribers can write up to 25,000 characters. X bios are capped at 160 characters, and display names at 50. One Chinese character counts as 1, not 2, toward the limit — Twitter uses code-point counting, not byte counting.
How many characters should a meta description be?
Google displays approximately 155–160 characters for meta descriptions on desktop. Descriptions over 160 characters are truncated with an ellipsis, typically cutting off your call to action. Mobile search results may truncate earlier (~120 characters). Best practice: write compelling copy within 155 characters and put your CTA within the first 130 for mobile safety.
Does an emoji count as 1 or 2 characters?
It depends on context. JavaScript's .length returns 2 for most emoji (UTF-16 surrogate pairs). Twitter counts code points (usually 2 for emoji above U+FFFF). For true visible-character counts, use Intl.Segmenter which returns 1. The ZWJ family emoji 👨👩👧👦 is visually 1 character but represents 4 code points and 11 UTF-16 units.
How many characters is an SMS text message?
A single SMS in GSM-7 encoding (basic Latin) is 160 characters. Including any emoji or non-GSM-7 character switches the message to UCS-2, dropping the limit to 70 characters per segment. Multi-segment messages use 7 header bytes per segment, leaving 153 chars (GSM-7) or 67 chars (UCS-2) of usable content per segment. Extended GSM-7 characters like € count as 2 characters each.
What is the Google title tag character limit?
Google enforces a pixel width limit (~600px), not a hard character count. In practice, 50–60 characters in typical fonts fits within this budget. Wider glyphs (like W, M, or Chinese characters) consume the pixel budget faster — a 55-character title with all caps or CJK characters may still get truncated while a 62-character lowercase title clears it. Moz's Title Tag Preview Tool tests pixel width directly.
How do I count characters in JavaScript without counting spaces?
Use text.replace(/\s/g, '').length for UTF-16 unit count without spaces. For Unicode-correct counting: Array.from(text.replace(/\s/g, '')).length. The \s pattern matches spaces, tabs, newlines, and other whitespace. Some academic publishers count without spaces to normalize across languages where word lengths vary significantly.
What is the LinkedIn post character limit?
LinkedIn posts are limited to 3,000 characters for personal profiles and Company Pages. LinkedIn articles have no enforced limit (up to ~125,000 characters in practice). Connection request messages: 300 characters. InMail messages: 2,000 characters. LinkedIn ads vary by format — Sponsored Content text is 150 characters; Headlines are 70 characters. LinkedIn bio: 2,600 characters.
Count Characters Instantly
Paste any text — tweets, meta descriptions, SMS drafts, email subjects — and get character count (with and without spaces), word count, sentence count, and reading time. Runs entirely in your browser. No data leaves your device.
Open Character Counter →