Skip to main content
ansicode

VPA — Vertical line position absolute (CSI d)

Move the cursor to an absolute row, keeping the current column.

Byte forms

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

\\x1b[\x1b[<row>d
\\033[\033[3d
\\e[\e[3d
ESC [ESC [ row d
hex1b 5b ... 64

Description

Vertical Position Absolute. Move the cursor to row <row> (1-based) without changing the column — the row-only counterpart of CHA (\x1b[<col>G, cursor-column). Default row is 1 if omitted (\x1b[d). Out-of-range values clamp to the visible region (or the DECSTBM scrolling region when DECOM is active). VPA's column-preserving behaviour makes it the right primitive when redrawing a status bar or progress line at a known row while the user types in the column above — CUP (\x1b[r;cH) would reset the column to 1. Less commonly used than CUP / CUU / CUD because most TUIs prefer absolute (row, col) pairs, but it's part of every ECMA-48-conformant terminal.

Spec citation: ECMA-48 §8.3.158 (VPA) / xterm-ctlseqs

Examples

bash
printf '\033[24dstatus: ok'   # jump to row 24, keep column
python
import sys; sys.stdout.write('\x1b[24dstatus: ok')
go
fmt.Print("\x1b[24dstatus: ok")
javascript
process.stdout.write('\x1b[24dstatus: ok')
c
printf("\x1b[24dstatus: ok");

Used in

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

  • vim / neovim status line redraw`:redrawstatus` and the per-window `&statusline` rebuild path use VPA to jump to the status row while preserving the column the cursor was sitting in — avoids a CUP `\x1b[<row>;<col>H` round-trip and the extra column-math when the buffer cursor is mid-line
  • htop / btop / glances column-aligned rowsafter writing the leftmost column of header / footer text, htop emits VPA to step down to the next row at the same column instead of recomputing the column for every row — the column constancy is the whole reason htop's vertical bars render cleanly under SIGWINCH redraws
  • ncurses `wmove(WIN, row, col)` when column unchangedncurses's diff renderer emits VPA `\x1b[<row>d` instead of CUP when its dirty-line tracker indicates the column matches the previous cursor column — a 1-2 byte saving per redraw that adds up on slow remote PTYs
  • less status-line repaintless repaints its `:` prompt by VPA to the bottom row + EL `\x1b[K` (clear) + write — the column was 0 from the previous newline so VPA suffices without a CUP
  • tmux pane-status-format updatestmux's per-pane status line update (`set -g pane-status-format`) emits VPA to land on the status row inside the pane viewport — `\x1b[<row>d` survives the per-pane translation cleanly even when panes are split horizontally and column origin differs

Frequently asked

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

How does \x1b[<n>d (VPA) differ from \x1b[<n>;<col>H (CUP)?
VPA (\x1b[Pn d) sets the cursor to row Pn *while preserving the current column*. CUP (\x1b[Pn1;Pn2 H) sets both row and column. Use VPA when you're walking down a column to draw a vertical sidebar / scrollbar / column-aligned status indicator without recomputing the column each row. Both row indices are 1-based per ECMA-48; \x1b[1d lands at the top row. If you need to clamp at the screen edge, the terminal handles it for you (most clamp to row 1 / row LINES).
Is \x1b[<n>d (VPA) 0-based or 1-based?
1-based, same as CUP. \x1b[1d is the top row; \x1b[0d is *not* the top row — Pn=0 is treated as Pn=1 per ECMA-48 (most terminals silently clamp; some legacy parsers reject the clause). Omitting Pn also defaults to 1: \x1b[d\x1b[1d. See pitfall cup-1-based-not-0-based for the same trap on CUP / HVP / CHA.

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

CSI cookbook · 2. Cursor movement — CUU / CUD / CUF / CUB + CUP + CHA / VPA