feat(renderer): add Mode 2031 dark/light theme detection#654
Closed
kavhnr wants to merge 2 commits intoanomalyco:mainfrom
Closed
feat(renderer): add Mode 2031 dark/light theme detection#654kavhnr wants to merge 2 commits intoanomalyco:mainfrom
kavhnr wants to merge 2 commits intoanomalyco:mainfrom
Conversation
Add support for the Mode 2031 terminal protocol for automatic dark/light
theme detection. This allows applications to reactively respond to OS
appearance changes in supported terminals (Ghostty 1.0+, kitty 0.38.1+,
Contour 0.4+, VTE 0.82+).
- Subscribe to theme notifications (CSI ? 2031 h) during setupTerminal
- Query initial theme mode (CSI ? 996 n) on startup
- Parse CSI ? 997 ; {1|2} n responses via themeModeHandler
- Emit 'theme_mode' event on the renderer when mode changes
- Expose themeMode getter returning 'dark' | 'light' | null
- Unsubscribe (CSI ? 2031 l) on destroy before other cleanup
- Export ThemeMode type from types.ts
a82566a to
15225a2
Compare
@opentui/core
@opentui/react
@opentui/solid
@opentui/core-darwin-arm64
@opentui/core-darwin-x64
@opentui/core-linux-arm64
@opentui/core-linux-x64
@opentui/core-win32-arm64
@opentui/core-win32-x64
commit: |
kommander
reviewed
Feb 9, 2026
packages/core/src/renderer.ts
Outdated
|
|
||
| this.queryPixelResolution() | ||
|
|
||
| this.writeOut("\x1b[?2031h") |
Collaborator
There was a problem hiding this comment.
Can we do this in terminal.zig setup and shutdown sequences?
Contributor
Author
There was a problem hiding this comment.
Yea we can/should. Moved to match how bracketed paste and focus tracking work. It only enables when the terminal actually reports support via DECRPM, which is how Ghostty handles mode activation in its state layer (https://github.com/ghostty-org/ghostty/blob/main/src/terminal/modes.zig) modes get toggled based on detected capability (not sent unconditionally)
Bonus: suspend/resume gets this for free. Updating now.
Move color scheme update (Mode 2031) enable/disable and initial query from TypeScript renderer into the Zig terminal setup and shutdown sequences, matching the existing pattern for focus tracking and bracketed paste. Add setColorSchemeUpdates helper for consistency with other mode toggles. The TS-side shutdown write was redundant as resetState() already handled it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ResponsiveTUI
Ghostty-capture-20-36-38.mp4
Summary
Adds support for the Mode 2031 terminal protocol, enabling automatic dark/light theme detection and live reactivity to OS appearance changes.
What this does
CSI ? 2031 h) duringsetupTerminal()CSI ? 996 n) on startupCSI ? 997 ; 1 n(dark) /CSI ? 997 ; 2 n(light) responses via a newthemeModeHandlerin the input handler pipelinetheme_modeevent on the renderer when the mode changesthemeModegetter returning"dark" | "light" | nullCSI ? 2031 l) on destroy, before other cleanupThemeModetype fromtypes.tsSupported terminals
Unsupported terminals silently ignore the escape sequences — no fallback needed.
Usage
Design decisions
The implementation follows patterns established by Neovim (PR #31350) and Helix (PR #14356):
CSI ? 996 nqueryfinalizeDestroy()terminfo_stoppattern — prevents stale notifications to subsequent processeswriteOut()interceptStdoutWritewhich captures but doesn't forward to the terminalfocusHandler, before key handlernullChanges
packages/core/src/renderer.ts—_themeModeproperty,themeModeHandler, subscribe/unsubscribe lifecycle,themeModegetterpackages/core/src/types.ts—ThemeModetype,theme_modeevent inRendererEventsReferences