Skip to main content
ansicode

CHT — Cursor Forward Tabulation (CSI I)

Advance the cursor N tab stops — the parameterized form of pressing Tab.

Byte forms

Every common string-literal form so you can paste-and-search either direction.

\\x1b[\x1b[NI
\\033[\033[1I
\\e[\e[1I
ESC [ESC [ N I
hex1b 5b <N> 49

Description

Cursor Forward Tabulation. Final byte I (0x49, uppercase i) moves the cursor forward to the next tab stop, repeated N times (default 1). It is the mirror of CBT (CSI Ps Z) and a parameterized form of HT (\t, 0x09). The terminfo cap is cht. Where HT moves a single tab forward, CHT lets you skip ahead by an arbitrary count without emitting N literal \t bytes, which matters in raw mode where \t may be consumed by line discipline.

Spec citation: ECMA-48 §8.3.10 (CHT)

Examples

bash
printf 'a\033[3Ib'   # cursor advances 3 tab stops, then writes b
python
import sys; sys.stdout.write('\x1b[3I')
go
fmt.Print("\x1b[3I")
javascript
process.stdout.write('\x1b[3I')
c
printf("\x1b[3I");

Used in

Real-world tools that emit this sequence — anchors the bytes to commands you've already used.

  • vim / neovim `<C-i>` jump-list-forwardvim's `<C-i>` jump-list-forward binding (the inverse of `<C-o>`) is terminologically distinct from Tab but shares the same keycode on most terminals — vim's terminal-mode redraw after `<C-i>` may emit CHT for cursor positioning in the displayed buffer rather than HT. `:set verbose=15` reveals the parameterized form
  • less / more horizontal scroll (`<Right>` in `-#` chop mode)less ≥ 633 with `--chop-long-lines` uses `\x1b[<N>I` to scroll the visible window N tab-stops to the right rather than emitting N space-padded redraws — keeps the existing screen content stable and just shifts the viewport
  • GNU indent / clang-format tab-aware reformat`indent --tab-size=4` and `clang-format --style='{IndentWidth: 4, UseTab: ForIndentation}'` emit CHT in their terminal-preview output (the `-d`/`--diff` modes) when the diff hunk crosses a custom tab grid set via TBC + HTS — avoids the visual-noise of N literal tab characters in colour-diff output
  • ncurses `wtab()` / column-aligned table widgetsncurses' `wtab()` (window-tab-advance) maps to the terminal's `cht` capability — visible in `infocmp xterm-256color | grep cht` (= `\E[%p1%dI`). Used by `nmtui`, `dialog`, `alsamixer` for aligning multi-column field labels without emitting variable-length HT runs
  • GNU pr column layout (`pr -t -m -w 80`)GNU `pr -t -m -w 80` (multi-column-merge mode) emits CHT between columns when stdout is a terminal and `LC_ALL` is set such that the tab grid isn't the default 8 — keeps column boundaries crisp without depending on the receiving terminal's HT interpretation

Frequently asked

Short answers to the questions developers actually search for this sequence.

When should I emit CHT (\x1b[I) instead of literal \t characters?
Three situations. (1) Raw mode without ONLCR / OPOST cooking — \t may still pass, but if your TTY discipline is fully raw and counting bytes for a column model, CHT with an explicit count gives you a single deterministic emission instead of N separate HT cells. (2) Skipping multiple stops in one move — \x1b[5I is one CSI vs. five \t bytes, lower latency over slow serial links and easier to log. (3) Inside DCS / OSC payloads where \t would be interpreted by the parser as data rather than a control. Most TUIs still use \t for prose output because it's terminfo-portable via ht; CHT (cht) is the parameterized form for cases above. CBT (\x1b[Z, see csi-cbt) is the symmetric reverse.
Does CHT count tab stops cleared by \x1b[3g?
No — CHT counts only *currently set* tab stops, so once you've issued TBC clear-all (\x1b[3g, see csi-tbc) and not re-set any stops via HTS (\x1bH, see esc-hts), \x1b[NI is a no-op regardless of N. The cursor stays put because the count refers to stops the cursor would *land on*, not columns it would *cross*. This is the same family of state-leak bug as \t going inert after a clear-all: any code that emits CHT for layout must either re-establish tab stops on entry or query DSR \x1b[6n after the move to detect that nothing happened. The defensive pattern is the same — pair \x1b[3g with a fresh HTS layout before yielding control.

Terminal support

xterm
yes
Linux console (fbcon)
yes
macOS Terminal.app
yes
iTerm2
yes
Windows Terminal
yes
cmd.exe / ConPTY
partial
kitty
yes
alacritty
yes
WezTerm
yes
Ghostty
yes
GNOME Terminal
yes
Konsole
yes
tmux
yes
GNU screen
yes

Related sequences