What Is a Grapheme Cluster?
A grapheme cluster is a sequence of one or more Unicode code points that users typically perceive as a single character.
On this page
Grapheme clusters at a glance
| Concept | Meaning |
|---|---|
| Grapheme cluster | One user-perceived character |
| Code point | One numeric Unicode value |
| Code unit | One unit used by UTF-8, UTF-16 or UTF-32 |
| Byte | An 8-bit storage unit |
| One grapheme equals one code point? | Not always |
| One grapheme equals one code unit? | Not always |
| Best use | Cursor movement, visible-character limits, deletion and truncation |
A grapheme cluster answers βWhat does the user see as one character?β Code points and code units describe lower-level Unicode and encoding representations.
What exactly is a grapheme cluster?
A grapheme cluster is a sequence of Unicode code points treated as one text element. It often corresponds to what a user perceives as one character, may contain one code point or several, and is a text-segmentation concept, not an encoding. It is independent of UTF-8 or UTF-16 storage and depends on Unicode grapheme-boundary rules used for cursor movement, deletion, selection and visible-character counting.
| Display | Code points | Grapheme clusters |
|---|---|---|
| A | U+0041 | 1 |
| Γ© | U+00E9 | 1 |
| eΜ | U+0065 U+0301 | 1 |
| ππ½ | U+1F44D U+1F3FD | 1 |
| πΊπΈ | U+1F1FA U+1F1F8 | 1 |
| π¨βπ©βπ§βπ¦ | Multiple code points | 1 |
Use the Unicode Sequence Analyzer or Unicode Character Counter to inspect these layers.
Grapheme cluster vs code point
A code point is one Unicode value. A grapheme cluster may contain one or several code points, so code-point count is not always visible-character count. Some visible characters use precomposed code points; others use a base character plus combining marks or emoji sequence code points.
Γ©
Precomposed: U+00E9
Decomposed: U+0065 U+0301
| Representation | Grapheme clusters | Code points |
|---|---|---|
| U+00E9 | 1 | 1 |
| U+0065 U+0301 | 1 | 2 |
The two strings may look identical while containing different code-point sequences. Read What Is a Unicode Code Point?, Code Points vs Code Units and try the Unicode Normalizer.
Grapheme cluster vs code unit
Code units belong to a particular encoding. UTF-8 uses 8-bit code units and UTF-16 uses 16-bit code units. One grapheme may require many code units, while grapheme count does not depend on whether text is stored as UTF-8 or UTF-16.
π
1 grapheme cluster
1 code point
4 UTF-8 code units
2 UTF-16 code units
π¨βπ©βπ§βπ¦ may be one grapheme cluster while using many code points, UTF-8 bytes and UTF-16 code units.
| Text | Graphemes | Code points | UTF-8 code units | UTF-16 code units |
|---|---|---|---|---|
| A | 1 | 1 | 1 | 1 |
| Γ© | 1 | 1 | 2 | 1 |
| eΜ | 1 | 2 | 3 | 2 |
| π | 1 | 1 | 4 | 2 |
| ππ½ | 1 | 2 | 8 | 4 |
Interactive grapheme-cluster analyzer
This local component segments text into clusters, shows boundaries and lists contained code points, bytes and UTF-16 code units.
Grapheme-cluster analyzer
Segment text into user-perceived characters and inspect the code points inside each cluster.
Limit: 2,000 UTF-16 code units. Input is never sent to the backend, analytics or logs.
Combining characters
A combining character modifies a preceding base character. Combining marks can represent accents, vowel signs and other modifications; they usually do not form separate user-perceived characters. Multiple marks may follow one base, and rendering depends on font support and shaping.
e + βΜ β Γ©
U+0065 U+0301
a + βΜ β Γ₯Combining sequences can be canonically equivalent to precomposed characters. Inspect with Unicode Character Inspector, Unicode Normalizer and Unicode Normalization Checker.
Emoji modifier sequences
Some emoji use a base emoji plus a modifier. Skin-tone modifiers are separate code points, and the combined sequence is usually one grapheme cluster. Removing part of the sequence changes the displayed result.
ππ½
U+1F44D THUMBS UP SIGN
U+1F3FD EMOJI MODIFIER FITZPATRICK TYPE-4
1 grapheme cluster
2 code points
8 UTF-8 bytes
4 UTF-16 code unitsThe exact artwork depends on the platform.
Zero-width joiner emoji sequences
U+200D is ZERO WIDTH JOINER. It can join emoji into a single displayed sequence while the code points remain separate. Composite emoji can represent a family, profession or other sequence, and unsupported platforms may show separate emoji.
π¨βπ©βπ§βπ¦
MAN
ZWJ
WOMAN
ZWJ
GIRL
ZWJ
BOYFlag emoji and regional indicators
Many flag emoji use pairs of regional indicator symbols. Each regional indicator is a separate code point, and a valid pair may display as one flag grapheme cluster. Unsupported systems may show separate symbols or letters.
πΊπΈ
U+1F1FA REGIONAL INDICATOR SYMBOL LETTER U
U+1F1F8 REGIONAL INDICATOR SYMBOL LETTER S
1 grapheme cluster
2 code points
8 UTF-8 bytes
4 UTF-16 code unitsVariation selectors
Variation selectors request a particular presentation. U+FE0E commonly requests text presentation, while U+FE0F commonly requests emoji presentation. The base character and variation selector may form one grapheme cluster, and selectors are usually invisible by themselves.
β₯
β₯οΈThe sequences can differ even when similar. Use the Invisible Character Detector and Unicode Sequence Analyzer.
What is an extended grapheme cluster?
Unicode text segmentation defines grapheme-boundary rules. Extended grapheme clusters are the practical default used by modern software because they handle combining marks, emoji sequences, regional indicators and script-specific behavior better than legacy clusters. For most application development, βgrapheme clusterβ should mean an extended grapheme cluster as defined by Unicode text-segmentation rules.
They are useful for interaction, but they do not perfectly match linguistic characters in every language. Boundaries can evolve as Unicode rules are updated, so applications should use maintained Unicode-aware libraries rather than manually coding a few special cases.
Grapheme boundaries and Unicode text segmentation
Unicode Standard Annex #29 defines default grapheme-cluster boundaries. Boundary rules determine where software may split text, using properties such as grapheme-break classes. Some boundaries are suppressed around combining marks, emoji modifiers, ZWJ sequences and regional indicators. Default segmentation may be tailored for specific languages or product behavior, and Unicode-version updates can change behavior.
A | Γ© | ππ½ | πΊπΈ | π¨βπ©βπ§βπ¦Each vertical line represents a grapheme boundary. The full algorithm belongs in maintained libraries, not ad hoc application code.
Why string length can be misleading
Standard length functions may count bytes, UTF-16 code units, code points or grapheme clusters. No single length function is correct for every requirement.
| Environment or function | Usually counts |
|---|---|
JavaScript String.length | UTF-16 code units |
JavaScript [...text].length | Code points |
JavaScript Intl.Segmenter | Grapheme clusters |
Python len(text) | Code points |
PHP strlen() | Bytes |
PHP mb_strlen() | Encoding-aware characters/code points |
| Grapheme-aware PHP functions | Grapheme clusters |
| Database byte-length function | Bytes |
| Database character-length function | Database-defined characters |
For π¨βπ©βπ§βπ¦, byte, code-unit, code-point and grapheme counts all differ.
Grapheme clusters in JavaScript
JavaScript strings use UTF-16 code units. String.length does not count visible characters, and spread syntax iterates code points, not grapheme clusters. Intl.Segmenter is preferred where available.
const text = "π¨βπ©βπ§βπ¦";
console.log(text.length);
console.log([...text].length);
const segmenter = new Intl.Segmenter("en", {
granularity: "grapheme",
});
const graphemes = [...segmenter.segment(text)];
console.log(graphemes.length); // Usually 1
console.log(graphemes.map(item => item.segment));function splitGraphemes(text, locale = "en") {
const segmenter = new Intl.Segmenter(locale, {
granularity: "grapheme",
});
return [...segmenter.segment(text)].map(item => item.segment);
}Check support and use a fallback library for older environments.
Grapheme clusters in Python
Python iteration normally yields code points, and len() does not count grapheme clusters. The standard library does not provide a direct extended-grapheme iterator, so a maintained library may be needed.
import regex
text = "π¨βπ©βπ§βπ¦"
graphemes = regex.findall(r"\X", text)
print(len(graphemes))
print(graphemes)text = "e\u0301"
print(len(text)) # 2 code points
print(len(regex.findall(r"\X", text))) # 1 grapheme cluster\X matches an extended grapheme cluster in Unicode-aware regex implementations. Pin and test the dependency against the Unicode version you use.
Grapheme clusters in PHP
PHP strings are byte sequences. strlen() counts bytes. mb_strlen() is multibyte-aware but should not automatically be described as grapheme-aware. The Intl extension provides grapheme functions when available.
$text = "π¨βπ©βπ§βπ¦";
echo strlen($text);
echo mb_strlen($text, "UTF-8");
echo grapheme_strlen($text);$text = "AΓ©ππ½";
$length = grapheme_strlen($text);
for ($index = 0; $index < $length; $index++) {
$cluster = grapheme_substr($text, $index, 1);
echo $cluster . PHP_EOL;
}Use intl for grapheme functions and mbstring for multibyte operations.
Safe text truncation
Byte-safe truncation is for payload, file-size and encoded storage limits and must not cut a UTF-8 sequence. Code-point-safe truncation applies only when the requirement limits Unicode scalar values and may still split a visible grapheme. Grapheme-safe truncation is usually right for usernames, display names, chat previews, buttons, labels, editor cursor behavior and user-facing character limits.
| Requirement | Correct unit |
|---|---|
| Maximum UTF-8 payload | Bytes |
| Maximum Unicode values | Code points |
| Maximum visible characters | Grapheme clusters |
| JavaScript API offset | Usually UTF-16 code units |
| Database field size | Depends on database definition |
| UI text truncation | Grapheme clusters |
Safe deletion and cursor movement
Backspace should normally remove one grapheme cluster. Cursor movement should avoid stopping between a base character and combining mark, and editors should avoid placing the caret inside a ZWJ emoji sequence. Selection boundaries should respect grapheme segmentation when practical. Low-level APIs may expose code-unit indexes even when the UI acts on grapheme clusters. Bidirectional text and shaping add complexity beyond grapheme segmentation.
Character limits in forms and usernames
Product requirements must specify the unit. Database capacity and API limits may be byte-based, security checks may inspect code points, and user-facing limits should usually count grapheme clusters. Normalization can alter code-point and byte counts, while confusable and invisible characters require separate validation. A grapheme limit alone does not prevent abuse with very long combining sequences.
- Validate encoded byte limit.
- Validate Unicode scalar values.
- Normalize only when required.
- Count grapheme clusters for user-facing limits.
- Limit pathological combining sequences.
- Detect invisible or disallowed controls.
- Apply product-specific script or identifier rules.
Use Invisible Character Detector, Confusable Character Detector, Unicode Normalizer and Unicode Character Counter.
Common grapheme-cluster mistakes
Treating JavaScript length as visible characters
It counts UTF-16 code units.
Treating spread syntax as grapheme-aware
Spread syntax iterates code points.
Treating Python length as grapheme count
It normally counts code points.
Treating mb_strlen() as sufficient for every UI limit
Multibyte-aware length and grapheme-aware length solve different problems.
Splitting text by arbitrary code-point indexes
Combining sequences and emoji can break.
Truncating UTF-8 by bytes without validation
This can create broken sequences.
Deleting only part of an emoji sequence
You can leave orphaned modifiers or joiners.
Assuming normalization produces one code point per grapheme
Normalized text can still contain multi-code-point clusters.
Assuming one grapheme has a fixed visual width
Emoji, CJK characters, combining marks and fonts vary.
Implementing segmentation with a simple regular expression
Unicode grapheme-boundary rules are more complex.
Ignoring Unicode-version changes
Segmentation data and rules evolve.
Practical debugging workflow
- Preserve the original text.
- Count encoded bytes.
- Inspect code points.
- Inspect UTF-16 code units where relevant.
- Segment grapheme clusters.
- Check normalization.
- Detect combining marks.
- Detect zero-width joiners and variation selectors.
- Test cursor movement and deletion.
- Test with accented text, emoji, flags and non-Latin scripts.
Start with Unicode Sequence Analyzer, Unicode Character Counter, Unicode Character Inspector, Unicode Normalizer, Invisible Character Detector and Unicode Text Compare.
Try these UnicodeNow tools
These tools help inspect graphemes, code points, byte length, normalization and hidden characters in real input.
Unicode Sequence Analyzer
Analyze code points, grapheme clusters, bytes, scripts and directionality.
Unicode Character Counter
Count code points, grapheme clusters, words, bytes and invisible characters.
Unicode Character Inspector
Inspect each Unicode character, encoding, category, script and normalization form.
Unicode Text Compare
Compare strings exactly and after Unicode normalization.
Unicode Normalizer
Normalize Unicode text to NFC, NFD, NFKC or NFKD.
Invisible Character Detector
Find zero-width, control, variation, private-use and spacing characters.
Text to Unicode Code Points
Convert text into U+XXXX Unicode code point notation.
UTF-8 Encoder and Decoder
Convert text to UTF-8 bytes and validate byte sequences.
Byte Length Calculator
Count UTF-8 bytes, code points, grapheme clusters and UTF-16 code units for text.
Frequently asked questions
What is a grapheme cluster?
A sequence of one or more Unicode code points that users generally perceive as one character.
Is a grapheme cluster the same as a character?
It is usually the closest technical unit to a user-perceived character, though linguistic and rendering behavior can be more complex.
Can one grapheme cluster contain multiple code points?
Yes. Combining characters, emoji modifiers, flags and ZWJ sequences commonly use multiple code points.
Is an emoji always one grapheme cluster?
Many displayed emoji are one grapheme cluster, but their internal code-point sequences vary, and unsupported rendering may display them differently.
Why is Γ© sometimes one code point and sometimes two?
It may use precomposed U+00E9 or decomposed U+0065 followed by U+0301.
Why does JavaScript report a large length for one emoji?
Because String.length counts UTF-16 code units rather than grapheme clusters.
Does Python len() count grapheme clusters?
No. It normally counts code points.
Does PHP have grapheme-aware functions?
Yes, when the Intl extension is available, functions such as grapheme_strlen() and grapheme_substr() can be used.
Should username limits count grapheme clusters?
Usually for user-facing length, but byte limits, security validation and pathological combining sequences must also be handled.
Can normalization change grapheme-cluster count?
It can change code-point sequences and may affect segmentation in some cases, so behavior should be tested.
Can one grapheme cluster contain many bytes?
Yes. Complex emoji and combining sequences can require many UTF-8 bytes.
Is grapheme segmentation the same in every Unicode version?
Rules and character data can evolve, so applications should use maintained Unicode-aware implementations.
References
- The Unicode Standard
- Unicode glossary
- Unicode Standard Annex #29: Unicode Text Segmentation
- Unicode Standard Annex #15: Unicode Normalization Forms
- Unicode Character Database
- Unicode emoji data
- MDN: Intl.Segmenter
- MDN: JavaScript strings
- Python regex package documentation
- PHP manual: grapheme functions
- ICU boundary analysis