HTS — Horizontal Tab Set (ESC H)
Set a tab stop at the current cursor column — partner to TBC.
Byte forms
Every common string-literal form so you can paste-and-search either direction.
\x1bH\033H\eHESC H1b 48Description
ECMA-48 §8.3.62 — Horizontal Tab Set (HTS). Two-byte ESC sequence: ESC (0x1b) followed by H (0x48). No CSI introducer, no parameters — the cursor's current column is the implicit operand. HTS places a tab stop at whatever column the cursor occupies right now; the companion to TBC (\x1b[g / \x1b[3g, see csi-tbc) which clears tab stops. Every terminal initialises with tab stops every 8 columns (cols 9, 17, 25, …) — HTS is the primitive you use to override that default 8-column grid with a custom layout that matches your form / table / column-aligned output. The terminfo capability for HTS is hts.
Portability — universal across xterm, iTerm2, kitty, alacritty, wezterm, gnome-terminal, Konsole, Ghostty, Linux console, and Windows Terminal. Windows legacy cmd.exe (pre-ConPTY) ignored HTS; ConPTY honours it. The canonical multi-stop layout-build workflow is: \x1b[3g to wipe the default 8-column grid → for each desired column, move there with HPA / CHA (\x1b[<col>G) and emit \x1bH → done. After that, every \t (HT, 0x09) or \x1b[<n>I (CHT — Cursor Forward Tabulation, see csi-cht) advances to the next custom stop. Tools that rely on the *default* tab grid (column, pr, expand, make recipe output) may produce surprising column alignment once you've redefined the grid — write defensively and clean up after yourself.
Two operational nuances. First, HTS state is per-terminal-window — not per-pane in tmux or screen. tmux normalises tab stops to the default 8-column grid on its inner pseudo-terminal, so an \x1bH emitted from inside a tmux pane is intercepted at the multiplexer boundary rather than forwarded to the outer terminal; the same applies to GNU screen. Second, the state is NOT touched by SGR 0, Ctrl+L (form-feed \x0c), or even a typical screen clear (\x1b[2J) — tab stops persist across screens, scrollback flushes, and most reset paths. Only \x1b[3g (TBC parameter 3 — clear all), DECSTR (\x1b[!p, see decstr-soft-reset), or RIS (\x1bc) will reset them. The safe defensive idiom for programs that need a known-clean tab grid (formatters, table layout, custom REPLs) is to emit \x1b[3g *before* setting their own stops, never assuming the default 8-column grid is intact — a previous program in the same session may have left a custom grid behind. Related: csi-tbc (TBC — the matching "clear tab stops" half), csi-cht (CHT — the parameterized "tab forward N stops" form of HT), c0-controls (the C0 table including HT \x09 — the literal tab character that triggers the advance).
Spec citation: ECMA-48 §8.3.62 (HTS)
Examples
printf '\033[3g' # clear all\nprintf '\033[10G\033H\033[20G\033H' # set tab stops at cols 10 and 20import sys; sys.stdout.write('\x1bH')fmt.Print("\x1bH")process.stdout.write('\x1bH')printf("\x1bH");Used in
Real-world tools that emit this sequence — anchors the bytes to commands you've already used.
- GNU `expand` / `unexpand` tab-grid metadata`expand -i -t 4,8,12,16` doesn't emit HTS itself (it expands tabs to spaces in the stream), but `unexpand --first-only -t 4,8,12,16` reading a preserved-tab stream emits HTS at each grid boundary in the terminal-preview output to restore the original tab-stop layout for round-trip fidelity
- vim / neovim `:set tabstop=4,8,12`vim's vimL `:set tabstop=4,8,12` (variable tabstops, ≥ 7.3) emits `\x1b[3g` + a series of CHA + `\x1bH` calls to reprogram the terminal's tab grid for the duration of the editor session — visible via `:set verbose=15`. Cleaned up via DECSTR on `:q`
- BSD ed `T` commandBSD `ed`'s `T` (set-tab) command, used in legacy mode `T8,16,24`, emits HTS at each specified column after a `\x1b[3g` clear-all — historically used to reformat punched-card-era data files where tab columns aren't multiples of 8. Still present in NetBSD / FreeBSD `ed(1)`
- terminfo `hts` cap (queryable via `tput hts`)`tput hts` looks up the `hts` capability and emits the terminal-specific HTS byte — `xterm-256color` and friends all map to `\x1bH` (the canonical two-byte form). Shell scripts that lay out custom column grids portably use `tput hts` rather than hard-coding `\033H`
- Emacs `tab-stop-list` recalculationGNU Emacs in `tty` frames emits HTS via `term-emulate-terminal` when `tab-stop-list` is set to a custom layout (`(setq tab-stop-list '(4 8 16 24))`) — paired with `\x1b[3g` at frame entry. Visible by trapping the TTY output with `script` and grep-ing for `\x1bH`
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
| xterm | Linux console (fbcon) | macOS Terminal.app | iTerm2 | Windows Terminal | cmd.exe / ConPTY | kitty | alacritty | WezTerm | Ghostty | GNOME Terminal | Konsole | tmux | GNU screen |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| yes | yes | yes | yes | yes | partial | yes | yes | yes | yes | yes | yes | yes | yes |
Related sequences
In the family cookbook
ESC cookbook · 2. Tab stops — `\x1bH` (HTS) sets, `\x1b[g` (TBC) clears