Grapheme clusters¶
Status: Extension. Mode 2027 is specified by the terminal-wg working group and originated in Contour.
A grapheme cluster is what a reader perceives as one character. Unicode Standard Annex 29 (UAX #29) defines the boundaries: a base plus combining marks, an emoji ZWJ sequence, a regional-indicator pair forming a flag, or a base plus skin-tone modifier are each one cluster. A classic terminal ignores clusters and measures code points one at a time.
Mode 2027¶
CSI ? 2027 h treat each grapheme cluster as one unit
CSI ? 2027 l measure code points individually (legacy)
CSI ? 2027 $ p query; reply CSI ? 2027 ; Ps $ y
The DECRQM reply value tells an application what to expect: 1 set, 2
reset, 3 permanently set, 4 permanently reset, 0 unrecognized. A reply
of 0 or no reply means the terminal does not know the mode and clusters are
not guaranteed.
What changes when it is set¶
With the mode set the terminal:
- stores one cluster in one cell run, whose width is the width of the cluster as a whole, normally 2 for emoji sequences and 1 or 2 for scripts with combining marks;
- does not advance the cursor for a joiner, modifier, or variation selector that extends the current cluster;
- treats a regional-indicator pair as one width-2 unit rather than two;
- applies the same rule to the cursor-position report, so a probe sees the clustered width.
With the mode reset, behavior is whatever the emulator did before; the spec does not require code-point-by-code-point measurement, only that the terminal be honest through the query.
Some emulators cluster unconditionally and report the mode as permanently set
(3). That is conforming.
Application behavior¶
- Query
CSI ? 2027 $ pwith a timeout. - On
1or3, measure strings with a UAX #29 segmenter and a cluster width function. - Otherwise, measure with
wcwidthper code point and avoid ZWJ sequences where alignment matters.
Libraries that already do this include unicode-width plus
unicode-segmentation (Rust), utf8proc, and libgrapheme.
Probe¶
stty -echo -icanon min 0 time 5
printf '\033[?2027$p'; dd bs=64 count=1 2>/dev/null | cat -v; echo
printf '\033[?2027h'
printf '\r\033[K👨👩👧\033[6n'; dd bs=64 count=1 2>/dev/null | cat -v; echo
stty sane
A clustering terminal reports column 3 after the family emoji; a legacy one reports 5 or 7.
Sources¶
- terminal-wg, grapheme cluster specification
- Unicode Standard Annex #29, Text Segmentation
- Contour VT extensions
Compatibility¶
Choose terminals
| Feature / stable ID | Alacritty | Apple Terminal | ConPTY (conhost) | Contour | DomTerm | foot | Ghostty | iTerm2 | kitty | Konsole | mintty | mlterm | PuTTY | Revenant | RLogin | st | tmux | rxvt-unicode | VTE | wayst | WezTerm | Windows Terminal | xterm | xterm.js |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Grapheme cluster mode (2027)dec-mode-2027-grapheme-clusters |
UnknownImported · unverified | Unknown | Unknown | SupportedImported · unverified | Unknown | SupportedImported · unverified | SupportedImported · unverified | Unknown | UnsupportedImported · unverified | Unknown | Unknown | Unknown | Unknown | Unknown | Unknown | Unknown | UnknownImported · unverified | Unknown | Unknown | Unknown | PartialImported · unverified | UnknownImported · unverified | UnknownImported · unverified | UnknownImported · unverified |