Skip to main content
ansicode

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.

\\x1b[\x1bH
\\033[\033H
\\e[\eH
ESC [ESC H
hex1b 48

Description

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

bash
printf '\033[3g'                # clear all\nprintf '\033[10G\033H\033[20G\033H'   # set tab stops at cols 10 and 20
python
import sys; sys.stdout.write('\x1bH')
go
fmt.Print("\x1bH")
javascript
process.stdout.write('\x1bH')
c
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

Related sequences

In the family cookbook

ESC cookbook · 2. Tab stops — `\x1bH` (HTS) sets, `\x1b[g` (TBC) clears