ANSI themes: text-opacity rasterizes ANSI colors to the Monokai fallback (HeaderTitle turns neon on blur)
Have you checked closed issues? (https://github.com/Textualize/textual/issues?q=is%3Aissue+is%3Aclosed) Yes
Have you checked against the most recent version of Textual? (https://pypi.org/search/?q=textual) Yes
The bug
Under an ANSI theme (ansi-dark / ansi-light), applying text-opacity < 100% to an ANSI-colored region renders it with hardcoded RGB from Rich's Monokai DEFAULT_TERMINAL_THEME instead of the terminal's own ANSI palette.
This happens out of the box with the built-in HeaderTitle. App.DEFAULT_CSS fades it on blur:
&:blur HeaderTitle {
text-opacity: 50%;
} So a header with background: ansi_green renders in the terminal's green while focused, but the moment the window loses focus (AppBlur), the title flips to a fixed #98e024 (neon lime) that ignores the terminal and matches no other ansi_green element in the app. text-opacity is a blend against the background, which needs concrete RGB; ANSI colors are symbolic, so they get resolved through Monokai (green = #98e024) to do the math.
Complete working example (runs without modification, no terminal or interaction needed, it drives AppBlur itself and prints the rendered styles):
import asyncio
from textual import events
from textual.app import App, ComposeResult
from textual.widgets import Header
class Repro(App):
CSS = "Header { background: ansi_green; color: ansi_black; }"
def on_mount(self) -> None:
self.theme = "ansi-dark"
def compose(self) -> ComposeResult:
yield Header()
def title_style(app):
for segment in app.screen._compositor.render_strips()[0]:
if segment.text.strip() and segment.text.strip() != "⭘":
return segment.style
async def main():
app = Repro()
async with app.run_test() as pilot:
await pilot.pause()
print("focused:", title_style(app))
app.post_message(events.AppBlur())
await pilot.pause()
print("blurred:", title_style(app))
asyncio.run(main())Output:
focused: color(0) on color(2)
blurred: #597d1f on #98e024 Focused, the title is symbolic ANSI (color(0) on color(2)) so the terminal picks the shade. Blurred, it is rasterized to Monokai RGB (#98e024).
Expected: under an ANSI theme the title stays in the terminal's palette on blur (either full ANSI green, or a genuinely dimmed ANSI color) rather than switching to a fixed off-palette RGB.
Actual: it renders as #98e024, Rich's Monokai green.
This is not header-specific, any text-opacity / opacity blend on an ANSI-colored widget under an ANSI theme hits it. The header is just the first place users see it, because the default blur rule triggers it on every focus change.
Workaround, for anyone who lands here, re-pin the title to full opacity:
Screen:blur HeaderTitle {
text-opacity: 100%;
}textual diagnoseTextual Diagnostics
Versions
| Name | Value |
|---|---|
| Textual | 8.2.8 |
| Rich | 15.0.0 |
Python
| Name | Value |
|---|---|
| Version | 3.14.6 |
| Implementation | CPython |
| Compiler | GCC 16.1.1 20260430 |
Operating System
| Name | Value |
|---|---|
| System | Linux |
| Release | 7.1.2-arch3-1 |
Terminal
| Name | Value |
|---|---|
| Terminal Application | Kitty |
| TERM | xterm-kitty |
| COLORTERM | truecolor |
Source: Textualize/textual