?2031 Dark and Light Mode Detection
Have the terminal tell the program whether its colors are dark or light, and again whenever they change.
CSI ? 2031 h/l
- Specification
- Dark and Light Mode detection, Contour VT extensions
A program that picks its own colors wants to know whether it is drawing on a dark background or a light one, and to hear about it when the desktop switches from one to the other, or the user changes the terminal’s profile. CSI ? 996 n asks, and the terminal answers with a private Device Status Report, CSI ? 997 ; 1 n for dark or CSI ? 997 ; 2 n for light. With CSI ? 2031 h set, the terminal sends the same report on its own whenever its colors change between the two; CSI ? 2031 l stops the reports.
It is neither xterm’s nor DEC’s. Its definition is Dark and Light Mode detection, kept by the Contour terminal’s authors with their other VT extensions, read at commit ab57ab3. It is short. The report is sent when the terminal’s palette changes because the user changed the profile, directly or by changing the operating system’s theme, and not otherwise. It does not say that setting the mode sends a report, nor how a terminal decides that its colors are dark, and terminals differ on both:
- Ghostty answers from its configuration’s theme state, which follows the operating system’s appearance when the configuration gives a light and a dark theme, and sends a report when that changes or the configuration is reloaded.
- kitty calls its colors dark when its default background is (
is_dark), and sends a report when its colors change other than by an escape code. - foot answers from which of its two themes is active,
[colors-dark]or[colors-light], and sends a report when it switches theme (csi.c,term_theme_switch_to_dark). - VTE calls its colors dark when its default background is no lighter than its default foreground, and sends a report when the program hosting it changes either of them, but not when an escape code does (
set_color,is_color_palette_dark).
None of them sends a report when the mode is set, so a program that wants the state at the start asks with CSI ? 996 n as well as setting the mode. A program finds out whether a terminal has it by asking with DECRQM: an answer of 0, not recognized, or no answer at all, means it does not.
A default xterm has no mode 2031. It answers DECRQM for it with 0, as it does for any mode it does not know (do_dec_rqm), and does not answer CSI ? 996 n, which falls through to the empty default of its DSR dispatch (CASE_DSR). The cases follow xterm.
libghostty-vt has the mode, report_color_scheme in modes.zig, off to begin with and reset by RIS, and answers DECRQM for it with 1 or 2, so the first case is a known difference. Whether the colors are dark is the host’s to say: CSI ? 996 n is answered only through a callback the host sets with GHOSTTY_TERMINAL_OPT_COLOR_SCHEME, and is dropped without one. This site’s wrapper sets none, so libghostty-vt here answers the query no more than xterm does. Sending a report when the colors change is the host’s job too, as Ghostty does from its own configuration.
Support
| Terminal | Version | Support | Notes |
|---|---|---|---|
| VT220 | manual, 2nd ed., 1984 | No | not in the manual |
| VT510 | manual, 1st ed., 1993 | No | not in the manual; not in Table 5-6 |
| xterm | patch 412, xterm-411a | No | not recognized; DECRQM reports 0, and CSI ? 996 n is not answered |
| libghostty-vt | 0f171f6 | Yes | COLORSCHEME-1: implements the mode; the query is answered through a callback the host sets, and reports are the host’s to send |
| Konsole | 26.11.70, a24c3d71 | No | no mode 2031 and no CSI ? 996 n |
| kitty | 0.49.2, its documentation | Yes | dark when the default background is (window.py, screen.c); since 0.38.1 |
| foot | 1.28.0 | Yes | dark when the [colors-dark] theme is active (csi.c); since 1.23.0 |
| VTE | 0.84.1, source | Yes | dark when the default background is no lighter than the foreground (vte.cc); since 0.82.0 |
Validation
COLORSCHEME-1: A default xterm does not recognize it
\e[?2031$p
\e[?2031h
\e[?2031$p
|____|
reply \e[?2031;0$y\e[?2031;0$y
COLORSCHEME-2: Setting it sends nothing
\e[?2031h
|____|
reply none
COLORSCHEME-3: A default xterm does not answer the query
\e[?996n
|____|
reply none
COLORSCHEME-4: The screen does not change
A
\e[?2031h
\e[?996n
\e[?2031l
B
|AB__|
cursor 1,3