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, v8.2.5
The bug
Creating a button (or any other widget that'll use content.from_markup) with a label that has an uppercase markup tag (like [RED]) will not render it since it'll be treated as if it were a tag. (buttons 1 and 2 in the example).
Using the markup.escape function will not prevent this, since its regex only targets markup blocks which are lowercase. (buttons 3 and 4 in the example).
The only workaround I've found is to instantiate a Content which will ignore markup from the whole text string (buttons 5 and 6 in the example).
I'm unsure if this would be considered a bug.
Complete example:
from textual.app import App, ComposeResult
from textual.content import Content
from textual.widgets import Button
from textual.markup import escape
class escapeApp(App):
BINDINGS = {
("q,ctrl+c", "quit", "Quit"),
}
def compose(self) -> ComposeResult:
yield Button("[red] non-escaped str with lowercase block", id="one")
yield Button("[RED] non-escaped str with uppercase block", id="two")
yield Button(escape("[red] escaped str with lowercase block"), id="three")
yield Button(escape("[RED] escaped str with uppercase block"), id="four")
yield Button(Content("[red] non-escaped Content (no markup) with lowercase block"), id="five")
yield Button(Content("[RED] non-escaped Content (no markup) with uppercase block"), id="six")
if __name__ == "__main__":
escapeApp().run()
Screenshot of example output:

It will be helpful if you run the following command and paste the results:
Textual Diagnostics
Versions
| Name |
Value |
| Textual |
8.2.5 |
| Rich |
15.0.0 |
Python
| Name |
Value |
| Version |
3.14.4 |
| Implementation |
CPython |
| Compiler |
Clang 21.0.0 (clang-2100.0.123.102) |
| Executable |
/Users/david/Downloads/tests/.venv/bin/python3 |
Operating System
| Name |
Value |
| System |
Darwin |
| Release |
25.4.0 |
| Version |
Darwin Kernel Version 25.4.0: Thu Mar 19 19:30:44 PDT 2026; root:xnu-12377.101.15~1/RELEASE_ARM64_T6000 |
Terminal
| Name |
Value |
| Terminal Application |
vscode (1.118.1) |
| TERM |
xterm-256color |
| COLORTERM |
truecolor |
| FORCE_COLOR |
Not set |
| NO_COLOR |
Not set |
Rich Console options
| Name |
Value |
| size |
width=98, height=19 |
| legacy_windows |
False |
| min_width |
1 |
| max_width |
98 |
| is_terminal |
True |
| encoding |
utf-8 |
| max_height |
19 |
| justify |
None |
| overflow |
None |
| no_wrap |
False |
| highlight |
None |
| markup |
None |
| height |
None |
PS: I found this behavior on iSponsorBlockTV's configurator, with a device that has the following name: [TV] MODEL_NUMBER
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, v8.2.5
The bug
Creating a button (or any other widget that'll use
content.from_markup) with a label that has an uppercase markup tag (like[RED]) will not render it since it'll be treated as if it were a tag. (buttons 1 and 2 in the example).Using the
markup.escapefunction will not prevent this, since its regex only targets markup blocks which are lowercase. (buttons 3 and 4 in the example).The only workaround I've found is to instantiate a
Contentwhich will ignore markup from the whole text string (buttons 5 and 6 in the example).I'm unsure if this would be considered a bug.
Complete example:
Screenshot of example output:

It will be helpful if you run the following command and paste the results:
Textual Diagnostics
Versions
Python
Operating System
Terminal
Rich Console options
PS: I found this behavior on iSponsorBlockTV's configurator, with a device that has the following name:
[TV] MODEL_NUMBER