# Horizontal Tabulation Set (HTS)

> Set a tab stop at the cursor's column.

- **Sequence:** `ESC H`
- **ECMA-48:** [§8.3.62 HTS – Character Tabulation Set, p. 46](https://archive.org/details/ecma-48-5th-edition-june-1991/page/n59/mode/1up)
- **DEC STD 070:** [§5.4.5 Horizontal Tabulation Set, p. 5-69](https://archive.org/details/bitsavers_decstandar0VideoSystemsReferenceManualDec91_74264381/page/n323/mode/1up)
- **VT220:** [§4.8 Tab Stops](https://vt100.net/docs/vt220-rm/chapter4.html#S4.8)
- **VT510:** [HTS—Horizontal Tab Set](https://vt100.net/docs/vt510-rm/HTS.html)

Set a horizontal tab stop at the column the cursor is in. The cursor does not
move, and the other tab stops are not affected. The next [HT](https://control-codes.page/c0/ht/index.html.md) or
[CHT](https://control-codes.page/csi/cht/index.html.md) from left of that column stops there.

HTS is a C1 control. ECMA-48 defines it both as the single byte `88` and as
`ESC H`, and the VT510 manual accepts either. libghostty-vt decodes its input
as UTF-8, so it accepts only `ESC H`: a lone `88` byte is not valid UTF-8 and
is printed as U+FFFD, the replacement character, and the code point U+0088
sent as UTF-8 is ignored.

[TBC](https://control-codes.page/csi/tbc/index.html.md) clears tab stops, one or all of them.

## Support

| Terminal | Version | Support | Notes |
| --- | --- | --- | --- |
| VT220 | [manual](https://vt100.net/docs/vt220-rm/), 2nd ed., 1984 | Yes | [§4.8 Tab Stops](https://vt100.net/docs/vt220-rm/chapter4.html#S4.8) |
| VT510 | [manual](https://vt100.net/docs/vt510-rm/), 1st ed., 1993 | Yes | [HTS](https://vt100.net/docs/vt510-rm/HTS.html) |
| xterm | patch 412, [xterm-411a](https://github.com/ThomasDickey/xterm-snapshots/tree/xterm-411a) | Yes |  |
| libghostty-vt | [0f171f6](https://github.com/ghostty-org/ghostty/tree/0f171f650a1cbbd6df4a2573b6b0c7de57cc6d90) | Yes | `ESC H` only; U+0088 is ignored |
| pyte | 0.8.2, [run as a library](https://git.jcollie.dev/jeff/control-codes/src/branch/main/emulators/results/pyte.json) | Yes | passes the 3 cases that show it working |
| xterm.js headless | 6.0.0, [run as a library](https://git.jcollie.dev/jeff/control-codes/src/branch/main/emulators/results/xtermjs.json) | Yes | passes the 3 cases that show it working |
| alacritty_terminal | 0.26.0, [run as a library](https://git.jcollie.dev/jeff/control-codes/src/branch/main/emulators/results/alacritty.json) | Yes | passes the 3 cases that show it working |
| ConEmu | build 230724, its [documentation](https://conemu.github.io/en/AnsiEscapeCodes.html) | No | not in ConEmu's [ANSI escape codes](https://conemu.github.io/en/AnsiEscapeCodes.html) |
| rxvt-unicode | 9.31, [run in a VM](https://git.jcollie.dev/jeff/control-codes/src/branch/main/emulators/results/urxvt.json) | Yes | passes the 3 cases that show it working |
| VTE | 0.84.1, [run as a library](https://git.jcollie.dev/jeff/control-codes/src/branch/main/emulators/results/vte.json) | Yes | passes the 3 cases that show it working |

## Validation

### HTS-1: Set a stop

Input, one step per line:

```text
\e[?5W    # reset tab stops: 9 and 17
\e[1;5H
\eH
\e[1;1H
\tX
```

Expected screen:

```text
|____X_______________|
```

### HTS-2: The other stops stay

Input, one step per line:

```text
\e[?5W    # reset tab stops: 9 and 17
\e[1;5H
\eH
\e[1;1H
\t\tX     # 5, then 9
```

Expected screen:

```text
|________X___________|
```

### HTS-3: After clearing every stop

Input, one step per line:

```text
\e[3g     # clear every tab stop
\e[1;12H
\eH
\e[1;1H
\tX
```

Expected screen:

```text
|___________X________|
```

### HTS-4: The 8-bit form, sent as UTF-8

Input, one step per line:

```text
\e[3g      # clear every tab stop
\e[1;12H
\u{88}     # U+0088 is HTS in 8 bits; ignored
\e[1;1H
\tX        # no stop was set
```

Expected screen:

```text
|___________________X|
```

---

This is the Markdown version of <https://control-codes.page/esc/hts/>. On that page every validation case runs live in libghostty-vt, the terminal emulation core of Ghostty, compiled to WebAssembly.

The validation cases are written in the notation described in <https://control-codes.page/notation/index.html.md>.
