# Control Sequence Introducer (CSI)

> Begin a control sequence: parameters, intermediates and a final byte.

- **Sequence:** `CSI Pm Pi Pf`
- **ECMA-48:** [§8.3.16 CSI – Control Sequence Introducer, p. 36](https://archive.org/details/ecma-48-5th-edition-june-1991/page/n49/mode/1up)
- **DEC STD 070:** [§3.5.3 Control Sequences, p. 3-23](https://archive.org/details/bitsavers_decstandar0VideoSystemsReferenceManualDec91_74264381/page/n127/mode/1up)
- **VT220:** [§2.5.2 Control Sequences](https://vt100.net/docs/vt220-rm/chapter2.html#S2.5.2)
- **VT510:** [4.3.3 Control Sequences](https://vt100.net/docs/vt510-rm/chapter4.html#S4.3.3)

CSI does nothing by itself. It starts a *control sequence*, and most of the
sequences on this site are control sequences. ECMA-48 gives the format in
[§5.4, p. 10](https://archive.org/details/ecma-48-5th-edition-june-1991/page/n23/mode/1up):

| Part | Bytes | What it is |
| --- | --- | --- |
| `CSI` | `ESC [` | the introducer |
| `P…P` | `0`–`9` `:` `;` `<` `=` `>` `?` | parameter bytes, if any |
| `I…I` | space and `!` to `/` | intermediate bytes, if any |
| `F` | `@` to `~` | the final byte, which ends the sequence |

The intermediate and final bytes together name the function: `CSI 2 J` is
ED, and `CSI 2 SP q`, with a space as intermediate, is something else
entirely. Final bytes `p` to `~` are set aside for private use, which is
where many of DEC's sequences live.

**Parameters.** Numbers are written in decimal and separated by `;`, as
ECMA-48 sets out in
[§5.4.2, p. 12](https://archive.org/details/ecma-48-5th-edition-june-1991/page/n25/mode/1up).
An empty parameter stands for the function's default: `CSI ; 5 H` is line 1,
column 5. Leading zeros do not count, and DEC STD 070 makes a parameter of
zeros alone the default too, which is why a `0` so often means `1`
([§3.5.3.1, p. 3-24](https://archive.org/details/bitsavers_decstandar0VideoSystemsReferenceManualDec91_74264381/page/n128/mode/1up)).
Trailing empty parameters may be left out, separators and all. A value too
large to hold should be taken as the largest the terminal supports.

**Private parameters.** A parameter string that starts with `<`, `=`, `>` or
`?` is private: its meaning is not standardized, and `CSI ? 6 h` (DECOM)
and `CSI 6 h` are different functions. DEC STD 070 says one of those bytes
anywhere else makes the whole sequence invalid, to be ignored up to and
including its final byte
([§3.5.3.4, p. 3-26](https://archive.org/details/bitsavers_decstandar0VideoSystemsReferenceManualDec91_74264381/page/n130/mode/1up)).
So does a parameter byte after an intermediate
([§3.5.3, p. 3-23](https://archive.org/details/bitsavers_decstandar0VideoSystemsReferenceManualDec91_74264381/page/n127/mode/1up)),
and so does `:`, which DEC STD 070 reserves
([§3.5.3.1, p. 3-24](https://archive.org/details/bitsavers_decstandar0VideoSystemsReferenceManualDec91_74264381/page/n128/mode/1up)). [SGR](https://control-codes.page/csi/sgr/index.html.md) is the exception everyone makes now,
with colons separating the parts of a color or underline style.

**Ending early.** A sequence ends at its final byte. CAN or SUB part-way
through abandons it, and an ESC abandons it and starts another; other
control characters inside one are carried out on the spot, as
[BEL](https://control-codes.page/c0/bel/index.html.md)'s page shows. A sequence the terminal does not implement is
ignored as if it had not arrived.

**The 8-bit form.** ECMA-48 also gives CSI as the single byte `9B`. A
terminal reading UTF-8 does not take it as CSI: the code point U+009B is
ignored, and a lone `9B` byte is not valid UTF-8. A default xterm ignores
that byte as well when printable text follows it, while libghostty-vt prints
it as U+FFFD, so that case is a known difference. Either way, what follows
is printed as text. The [UTF-8](https://control-codes.page/parser/utf8/index.html.md) page has the details.

## Validation

### CSI-1: Empty and zero parameters take the default

Input, one step per line:

```text
\e[;3H    # line left out
A
\e[000;002H
B
\e[3;H    # column left out
C
```

Expected screen:

```text
|_BA___|
|______|
|C_____|
```

### CSI-2: A value too large is taken as large as possible

Input, one step per line:

```text
\e[99999999;2H
X
```

Expected screen:

```text
|______|
|______|
|_X____|
```

### CSI-3: Malformed sequences are ignored

Input, one step per line:

```text
\e[2?3H   # private marker in the middle
\e[2\s3H  # parameter after an intermediate
\e[2:3H   # a colon
X
```

Expected screen:

```text
|X_____|
|______|
|______|
```

### CSI-4: An unknown sequence is ignored

Input, one step per line:

```text
AB
\e[5~     # a function key code, not a control function here
C
```

Expected screen:

```text
|ABC___|
cursor 1,4
```

### CSI-5: The 8-bit forms

*A known difference: libghostty-vt does not pass this case.*

Input, one step per line:

```text
\x9b      # a lone byte: xterm ignores it before text
2;2H
```

Expected screen:

```text
|2;2H__|
|______|
```

### CSI-6: U+009B is not CSI either

Input, one step per line:

```text
\u{9b}    # U+009B as UTF-8: ignored
2;2H
```

Expected screen:

```text
|2;2H__|
|______|
```

---

This is the Markdown version of <https://control-codes.page/esc/csi/>. 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>.
