Scroll Position Protocol (mode 7502)
Draft 0.1 · October 12, 2026
A small terminal protocol that lets a full-screen program share its viewport with the terminal: which rows of its content are on screen, and where. The terminal can show its native scrollbar beside those rows, and dragging it asks the program to scroll.
# The program turns the protocol on and reports rows 120–146 of 400, # in a scrolling area that starts on the second screen row. CSI ? 7502 h OSC 7502 ; top=120:rows=27:total=400:y=1 ST # The user drags the scrollbar to the top. 373 is the last position. CSI 7502 ; 0 ; 373 v
Note: The protocol isn't tied to any terminal or program, and leaves presentation to the terminal: a scrollbar is the obvious use, but the same reports could drive a minimap or a screen reader. Reports use key=value pairs like kitty's extensions and OSC 7501, and turning it on works like synchronized output (mode 2026).
Motivation
On the primary screen, the terminal scrolls its own scrollback, with a native scrollbar, momentum, and search. Full-screen programs such as coding agents, pagers, and editors run on the alternate screen, which has no scrollback, so they scroll themselves and at best draw a scrollbar out of text cells. This protocol lets them report their position, so the terminal can show its own scrollbar and pass drags back as requests.
Conventions
MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are used as in RFC 2119 and RFC 8174, only in capitals.
| Name | Bytes |
|---|---|
ESC | 0x1B |
CSI | ESC [ |
OSC | ESC ] |
ST | ESC \ or BEL (0x07) |
A terminal is one window, tab, or pane on a pseudo-terminal, and a program writes to it. The scrolling area is where the program's content scrolls, such as a transcript above an input box, and a row is one screen row of it. Rows count from 0.
Sequences
| Direction | Sequence | Meaning |
|---|---|---|
| program → terminal | CSI ? 7502 $ p | Query (DECRQM): is it supported? |
| terminal → program | CSI ? 7502 ; Ps $ y | Reply (DECRPM): whether it is. |
| program → terminal | CSI ? 7502 h | Turn the protocol on. |
| program → terminal | CSI ? 7502 l | Turn it off, clearing the report. |
| program → terminal | OSC 7502 ; pairs ST | Report: where the scrolling area is. |
| program → terminal | OSC 7502 ; ST | Clear: there is no scrolling area. |
| terminal → program | CSI 7502 ; T ; M v | Request: show row T first. T = M is the end. |
Detection and turning it on
program → CSI ? 7502 $ p program → CSI c terminal → CSI ? 7502 ; 2 $ y terminal → CSI ? 62 ; 22 c program → CSI ? 7502 h
The program asks with DECRQM, then primary device attributes (CSI c), which every terminal answers. A reply of 1, 2, or 3 before the DA1 reply means supported. 0, 4, or the DA1 reply first means not, and the program keeps its own scrollbar.
- Terminals answer in order. One that won't show a scrollbar, for example because the user turned scrollbars off, MUST reply 0 or 4. Nothing tells a program if that changes later, so a terminal SHOULD keep showing reports while the mode is on.
- The program turns the mode on after entering the alternate screen (
?47,?1047, or?1049) and stops drawing its own scrollbar. It MUST turn the mode off before handing the terminal to anything else, such as a shell or an editor. - The terminal acts only while the mode is on and the alternate screen is shown. Otherwise it MUST ignore reports and MUST NOT send requests. Leaving the alternate screen and
RISturn the mode off;DECSTRdoesn't.
Report
Pairs are key=value, separated by :, in any order, with decimal integer values.
| Key | Required | Meaning |
|---|---|---|
top | yes | The first row shown. |
rows | yes | The scrolling area's height. At least 1. |
total | yes | All rows of the content. |
y | no | The screen row the scrolling area starts on. Default 0. |
- Each report replaces the last. A terminal MUST ignore a report with a missing required key, a non-integer value, an empty pair,
rows=0, pairs over 256 bytes, or a value over 2147483647 (231 − 1). It MUST ignore unknown keys. If a key repeats, the last value wins. - A terminal clamps
topto0…max(0, total - rows), and hides the scrollbar whiletotalis at mostrowsor the area doesn't fit on the screen. It MUST forget the report on a clear, when the mode turns off, and when the alternate screen is left. - A program SHOULD report only while the mode is on, right after drawing a frame that changes a value, and SHOULD NOT repeat an unchanged report. With synchronized output, it reports inside the update, and the terminal applies the report with that frame.
- Reports describe the primary scrolling area. A later revision may add others, marked with an
idkey. Until then, terminals MUST ignore reports with one, and programs MUST still send the primary report. topandtotalMAY be estimates. A request only means something against the report it was made from.
Request
When the user moves the scrollbar, the terminal sends CSI 7502 ; T ; M v, where M is total - rows of the latest report and T is from 0 to M. Programs ignore any further parameters. Plain CSI … v is unused by key and mouse encodings and by terminals.
- A program MUST NOT treat a request as typed input.
T = Mmeans the end: the program shows its last rows and, if it follows new output, follows it again, even if its content has grown. Otherwise it shows rowTfirst, clamped to its content. Then it draws and reports. - Only the user's own moves send requests, never a scrollbar moved by a report. A terminal MAY skip positions during a drag but MUST send the final one, and a program MAY skip all but the latest.
- Turning the mode off, a clear, or leaving the alternate screen ends a drag. The mouse wheel still goes through mouse reporting.
Multiplexers
A multiplexer that doesn't know the protocol drops the mode, so programs inside it see it as unsupported. One that supports it implements it per pane, like mouse reporting, instead of passing the sequences through: replies and requests would reach the multiplexer, not the program.
Security
Reports are untrusted, but carry only bounded numbers and only move a scrollbar. Requests carry only numbers from the user's drag, and are ordinary input to whatever program reads the terminal, which is why programs turn the mode off before handing it on.
Relationship to other sequences
- Mouse reporting (
?1000,?1006) and alternate scroll (?1007) deliver the wheel but don't say where the program is. They are unchanged. - In-band resize (mode 2048) uses the same opt-in: a DEC mode that turns on CSI events. After a resize, the program reports again.
- mintty's application scrollbar (2019) is the closest precedent:
CSI pos ; size ; height # t, with events back asCSI … # eandCSI … # d. It has no detection or opt-in, spans the whole window, and snaps the thumb back after a drag. - terminal-wg #5 (2019) discussed the same idea and stalled on drag races, which absolute requests carrying
Maddress.
Implementation notes
Not requirements: what worked in the prototypes.
- During a drag, a terminal keeps the thumb under the pointer and sends at most one request per display refresh. Afterwards it holds the thumb until the next report, or about 250 ms.
- A terminal library reports support only when its embedding app draws the scrollbar.
- A program reads the DECRPM and DA1 replies itself, with a timeout, before a key parser sees them: crossterm 0.29 merges an unknown
CSI ?reply into the next one. Many CSI parsers keep parameters in 16 bits, so a program with more than 65535 rows parses requests itself.
Example
# After detection, on entering the alternate screen program → CSI ? 7502 h program → OSC 7502 ; top=373:rows=27:total=400:y=1 ST # The user drags to the top, then more output arrives terminal → CSI 7502 ; 0 ; 373 v program → OSC 7502 ; top=0:rows=27:total=400:y=1 ST program → OSC 7502 ; top=0:rows=27:total=412:y=1 ST # The user drags to the bottom while output streams in. # T = M, so the program follows the end, now at 416 rows. terminal → CSI 7502 ; 385 ; 385 v program → OSC 7502 ; top=389:rows=27:total=416:y=1 ST # Before leaving the alternate screen program → CSI ? 7502 l
Revision history
| Date | Revision | Changes |
|---|---|---|
| 2026-10-12 | 0.1 | Initial draft. |