Letras Diferentes
Unicode research · Grapheme boundaries and editors

Extended Grapheme Cluster Boundaries: UAX #29 Rules, Emoji and Editing

UAX #29 extended grapheme clusters, GB rules, emoji ZWJ and regional indicators, Indic conjuncts, Hangul and CRLF handling, plus Intl.Segmenter tests.

Technical referenceUnicode baseline: 18.0.0

A user-perceived character is not necessarily one Unicode scalar

Unicode text is a sequence of code points, but what a person perceives as a single character can include multiple scalars. An accented letter may use a base plus combining mark; an emoji flag can use two regional indicators; and an emoji family sequence may join several pictographs with U+200D ZERO WIDTH JOINER. Extended grapheme clusters in Unicode Standard Annex #29 provide a standardized default segmentation for these cases and many script-specific combinations.

A grapheme cluster is a text-boundary unit, not a guaranteed single font glyph, a byte or a UTF-16 code unit. Layout engines may render one cluster using multiple glyphs, and some typography combines information spanning more than one grapheme cluster. A text editor should use grapheme boundaries for many cursor and deletion operations but may still tailor editing behavior to language or application requirements.

Extended versus legacy grapheme clusters

UAX #29 distinguishes legacy and extended grapheme-cluster rules. Extended clusters include additional no-break rules for spacing marks, prepended characters and certain Indic conjunct behavior. They are the generally recommended default for user-facing text processing. A product should name which variant and which Unicode data version it implements rather than saying only “Unicode characters.”

The normative rules use properties such as Grapheme_Cluster_Break, Extended_Pictographic and Indic conjunct break properties, not generic intuition from visible appearance. Versioned property data comes from the Unicode Character Database and emoji data. A new Unicode release can extend the set of characters covered by individual grapheme rules, so results may vary across outdated runtimes.

Core no-break and break rules

UAX #29 rule familyBehaviorTypical case
GB3No break between CR and LF\r\n one cluster boundary unit
GB4–GB5Break around most controlsText boundary near control scalars
GB6–GB8Do not break valid Hangul syllable sequencesJamo L, V and T combinations
GB9No break before Extend or ZWJBase plus combining mark
GB9a, GB9bSpacingMark / Prepend behaviorMany Indic and other scripts
GB9cPreserve defined Indic conjunct sequencesConsonant-linker-consonant patterns
GB11Keep qualifying emoji ZWJ sequence togetherExtended_Pictographic ... ZWJ ... pictograph
GB12–GB13Pair regional indicatorsFlag sequences
GB999Break elsewhereOrdinary adjacent letters

The simplified descriptions above do not replace normative rule expressions. For example GB11 requires the appropriate Extended_Pictographic Extend* ZWJ × Extended_Pictographic context, not an unconditional “never break after ZWJ.” GB9c likewise depends on Indic conjunct properties and the required sequence context; arbitrary virama-like character combinations do not automatically form one cluster.

Concrete clusters and scalar counts

ExampleScalarsExpected default EGCs
A U+004111
e + U+030121
U+1F1FA U+1F1F8 (US flag)21
U+1F469 U+200D U+1F4BB (woman technologist)31
\r\n21
A B22
U+1100 U+1161 (Hangul L and V)21

Grapheme-cluster counts do not imply that every cluster renders as a single square, emoji, or cell. Emoji rendering depends on platform fonts and supported emoji sequences. A Unicode-valid sequence may appear as separate symbols on a system with older emoji support even when the default grapheme segmentation keeps it together.

Regional indicator pairing is contextual

Unicode emoji flags often use two regional indicator symbols rather than one encoded flag character. GB12 and GB13 pair regional indicators according to parity of the preceding consecutive regional-indicator run. This means segmentation for three regional indicators differs from blindly joining every adjacent pair with no regard for the run's boundary. For example three indicators ordinarily form a pair and a remaining singleton; it is not one three-scalar grapheme cluster.

A practical regression fixture is the scalar sequence U+1F1FA U+1F1F8 U+1F1E8: it should split into two clusters (the first pair, then the extra symbol). If an application removes the middle regional indicator, the remaining symbols may form a different pair; text edits therefore require recomputing segmentation around the edited region rather than caching an invariant one-symbol-one-cluster relationship.

Emoji ZWJ chains and variation selectors

U+200D ZERO WIDTH JOINER can connect emoji characters into a single sequence where UAX #29's GB11 context is met. Emoji modifiers and U+FE0F VARIATION SELECTOR-16 also participate in sequences through grapheme properties. The exact rendered result requires emoji-presentation and font support beyond segmentation; a valid cluster may be presented as multiple emoji glyphs on an older operating system.

Not every occurrence of U+200D must join neighboring characters into a meaningful combined pictograph. Character property context matters, and the Unicode Emoji standard (UTS #51) defines further recommended and supported sequences. A cluster algorithm should not perform arbitrary replacement of all U+200D-separated scalars with a claimed “single emoji.”

Indic conjunct handling and limits

UAX #29 includes GB9c to reduce undesirable breaks within specified Indic consonant and linker sequences. Such text can include consonants, virama-like linkers and extending marks. The default extended grapheme algorithm is still a segmentation approximation and is not the same as a full orthographic-syllable or script-shaping model. Script-sensitive editors may need tailored boundaries for a specific language.

For many Indic scripts, the visible glyphs depend on shaping behavior, ligature support and cluster reordering. It is a mistake to infer Unicode scalar count from glyph count. Normalization, grapheme segmentation and shaping belong to different layers and require independent tests.

Intl.Segmenter: robust counting and deletion units

The JavaScript Intl.Segmenter API provides locale-aware segmentation with granularity: "grapheme" where supported. Its iterator returns segment strings and their UTF-16 starting offsets, making it useful for cursor ranges and user-facing counters. JavaScript String.length, by contrast, counts UTF-16 code units; spreading into an array iterates code points, not grapheme clusters.

const seg = new Intl.Segmenter("en", {granularity:"grapheme"});
const sample = "e\u0301🇺🇸👩‍💻";
const clusters = [...seg.segment(sample)].map(x => x.segment);
console.log(clusters.length); // 3
console.log([...sample].length); // 2 + 2 + 3 = 7 scalars
console.log(sample.length); // more than 7 UTF-16 code units
console.log(clusters); // ["é", "🇺🇸", "👩‍💻"]

A code-point count computed using [...sample] is useful for scalar inspection, while a grapheme count serves user-facing character limits. Neither alone solves UTF-8 byte limits. The correct metric is dictated by the product's data contract rather than by an assumption that all three counts are interchangeable.

Cursor movement, backspace and safe truncation

For a simple text editor, deleting one extended grapheme cluster usually avoids leaving an isolated accent or half of a flag. Yet some applications intentionally edit inside a cluster, such as removing a combining mark, changing the skin-tone modifier or interacting with a complex Indic syllable. The default algorithm supplies a consistent starting point, not a prohibition against application-specific editing policies.

To truncate by visible-character count, enumerate grapheme segments and join only complete segments. Avoid slicing a JavaScript string by raw UTF-16 units: it can split supplementary scalar surrogate pairs. If the service imposes a separate UTF-8 byte budget, a safe truncation routine must measure encoded bytes after selecting complete clusters.

function takeGraphemes(text, maxCount) {
  const segmenter = new Intl.Segmenter("en", {granularity:"grapheme"});
  return [...segmenter.segment(text)].slice(0,maxCount)
    .map(x=>x.segment).join("");
}
console.log(takeGraphemes("e\u0301🇺🇸👩‍💻",2)); // "é🇺🇸"
// Do not implement this as text.slice(0,2).

Streaming segments and edited-boundary context

A text stream delivered in arbitrary chunks can split a grapheme cluster between buffers. An incremental segmenter must retain enough context to decide whether new scalars extend the previous cluster. Regional indicator parity and contextual emoji ZWJ rules are examples where the preceding sequence matters. Counting clusters within separate chunks and summing them can overcount the complete stream.

The appropriate boundary algorithm should be run against the complete logical text or through an incremental segmenter with a tested state machine. Normalizing each chunk separately is also unsafe, but normalization and segmentation are different problems; solving one boundary issue does not automatically solve the other.

Property-data conformance and practical cases

FixtureExpected cluster behavior
e plus U+0301No break before combining accent
\r\nNo boundary between CR and LF
Two regional indicatorsOne cluster for the pair
Three consecutive regional indicatorsOne pair, one singleton
Woman + ZWJ + laptopOne cluster under GB11 context
Hangul L + V JamoNo break under GB6
Arbitrary two ordinary Latin lettersBoundary between letters
Unknown newer pictographResult depends on Unicode property-data version

For full conformance, use the Unicode version-matched GraphemeBreakTest.txt dataset, and supplement it with language- and platform-specific editor behavior. When inspecting copyable decorative text from the US Fancy Text tool, test exact scalars, grapheme boundaries and rendering separately: passing one of these checks does not imply the other two.

Standards and implementation references

  1. UAX #29 — Unicode Text Segmentation
  2. UTS #51 — Unicode Emoji
  3. UAX #44 — Unicode Character Database
  4. UCD — GraphemeBreakTest.txt
  5. MDN — Intl.Segmenter
  6. UAX #15 — Unicode Normalization Forms

Unicode text segmentation is a standardized default. Application-specific tailoring and font rendering must be tested separately.