Repository: Textualize/textual

Issue:
Button doesn't render Rich Markup correctly
If I'm reading the docs correctly, the `Button` widget *should* accept Rich Markup for its `label`, but in 0.53.1, it doesn't seem to do this.

Here's my test app:

```python3
from textual.app import App
from textual.widgets import Button, SelectionList
import sys

class MyApp(App):
	def compose(self):
		if len(sys.argv) > 1:
			yield SelectionList(('one', 1), ('two', 2))
		yield Button(label='[red]hell[/]o')

MyApp().run()
```

And here are my findings regarding versions:

# Textual 0.39.0 and 0.40.0
## Two `Widget`s
If run with any argument, the app behaves exactly as expected, i.e. renders red "hell" and uncolored "o":

![v0 40 0-double](https://github.com/Textualize/textual/assets/2834/9314ddad-b92f-4214-a6af-b757096bd2fe)

## One `Widget`
However if the Button is the only thing to be rendered, i.e. when you run it without any arguments, then the meaning of "red" becomes somehow inverted:

![v0 40 0-single](https://github.com/Textualize/textual/assets/2834/34e32234-9c1a-463b-90e8-de9a9f0bae36)

# Textual 0.41.0 – 0.51.3
## Two `Widget`s:
Here markup seems totally ignored:
![v0 41 0-double](https://github.com/Textualize/textual/assets/2834/9405bc18-9329-4a89-8303-92a5948982d7)

## One `Widget`:
When run without arguments, we still see the inverted text, even in the latest version:
![v0 41 0-single](https://github.com/Textualize/textual/assets/2834/a5155529-db4a-4301-9ad3-d2770a686070)

# Thoughts
I would like to add Rich Markup to `Button` labels to indicate hotkeys for those buttons nicely, however for some reason latest version doesn't let me do that. How can I get Rich Markup to work on `Button` labels?

What causes the weird inversion behaviour depending on how many `Widget`s the app is rendering, is that normal?

Am I doing something else wrong?

# Diagnostics
My `textual diagnose` output (on version 0.53.1):

<!-- This is valid Markdown, please paste the following directly in to a GitHub issue -->
# Textual Diagnostics

## Versions

| Name    | Value  |
|---------|--------|
| Textual | 0.53.1 |
| Rich    | 13.7.1 |

## Python

| Name           | Value                                            |
|----------------|--------------------------------------------------|
| Version        | 3.11.2                                           |
| Implementation | CPython                                          |
| Compiler       | GCC 12.2.0                                       |
| Executable     | /home/goblin/venv/bin/python3 |

## Operating System

| Name    | Value                                               |
|---------|-----------------------------------------------------|
| System  | Linux                                               |
| Release | 6.1.0-11-amd64                                      |
| Version | #1 SMP PREEMPT_DYNAMIC Debian 6.1.38-4 (2023-08-08) |

## Terminal

| Name                 | Value          |
|----------------------|----------------|
| Terminal Application | *Unknown*      |
| TERM                 | xterm-256color |
| COLORTERM            | truecolor      |
| FORCE_COLOR          | *Not set*      |
| NO_COLOR             | *Not set*      |

## Rich Console options

| Name           | Value               |
|----------------|---------------------|
| size           | width=80, height=24 |
| legacy_windows | False               |
| min_width      | 1                   |
| max_width      | 80                  |
| is_terminal    | True                |
| encoding       | utf-8               |
| max_height     | 24                  |
| justify        | None                |
| overflow       | None                |
| no_wrap        | False               |
| highlight      | None                |
| markup         | None                |
| height         | None                |




Discussion:
We found the following entry in the [FAQ](https://textual.textualize.io/FAQ/) which you may find helpful:


- [Does Textual support images?](https://textual.textualize.io/FAQ/#does-textual-support-images)

Feel free to close this issue if you found an answer in the FAQ. Otherwise, please give us a little time to review.

This is an automated reply, generated by [FAQtory](https://github.com/willmcgugan/faqtory)
FYI, I was running everything in a `venv` and only changing the version of `textual` with things like `pip install textual==0.40.0`. Rich and everything else remained the same throughout my runs (Rich is on 13.7.1).
The reason for the difference when the `Button` is the only widget is that it will have focus. If you press <kbd>Tab</kbd> on your example that has several widgets, you'll see the same styling after the button is focused.

https://github.com/Textualize/textual/blob/5170a64bb2271924ca7a6b8650177119b925029f/src/textual/widgets/_button.py#L53-L54

The missing Rich markup looks like it _might_ be a regression after #3539.
Focus is such an obvious thing I didn't think of, thanks!

The point of using Rich markup in the label was to show hotkeys in a different colour, i.e. have `c` be in a different color in a string like `'([green]c[/])onfirm'`. I am now also wondering if it's possible to specify a CSS class for a *part* of a `Button` label: instead of a direct "green" colour, specify a CSS class, similarly to `<span class="hotkey">c</span>onfirm`. This should make it easier to add themes later. Is this doable? Can I maybe glue up a TextType from various subtexts of different CSS classes?
Sorry but you've lost me there as Textual CSS works at the widget level,. I think using Rich markup is probably the right approach, but there's been a few changes to how the `Button` is rendered since v0.40.0 and it seems the markup isn't properly taken into account.
OK, if it works at the widget level then styling parts of a label doesn't make sense. Sorry for confusion, thanks for the answer.

So yeah, the main issue here is with the regression introduced somewhere around v0.41.0.

Resolving patch:
diff --git a/src/textual/widgets/_button.py b/src/textual/widgets/_button.py
--- a/src/textual/widgets/_button.py
+++ b/src/textual/widgets/_button.py
@@ -227,7 +227,7 @@ def validate_label(self, label: TextType) -> Text:
     def render(self) -> RenderResult:
         assert isinstance(self.label, Text)
         label = self.label.copy()
-        label.stylize(self.rich_style)
+        label.stylize_before(self.rich_style)
         return HorizontalPad(
             label,
             1,


Test patch:
diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
--- a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
+++ b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
@@ -2321,6 +2321,171 @@
   
   '''
 # ---
+# name: test_button_with_console_markup
+  '''
+  <svg class="rich-terminal" viewBox="0 0 994 635.5999999999999" xmlns="http://www.w3.org/2000/svg">
+      <!-- Generated with Rich https://www.textualize.io -->
+      <style>
+  
+      @font-face {
+          font-family: "Fira Code";
+          src: local("FiraCode-Regular"),
+                  url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Regular.woff2") format("woff2"),
+                  url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Regular.woff") format("woff");
+          font-style: normal;
+          font-weight: 400;
+      }
+      @font-face {
+          font-family: "Fira Code";
+          src: local("FiraCode-Bold"),
+                  url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Bold.woff2") format("woff2"),
+                  url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Bold.woff") format("woff");
+          font-style: bold;
+          font-weight: 700;
+      }
+  
+      .terminal-2679207537-matrix {
+          font-family: Fira Code, monospace;
+          font-size: 20px;
+          line-height: 24.4px;
+          font-variant-east-asian: full-width;
+      }
+  
+      .terminal-2679207537-title {
+          font-size: 18px;
+          font-weight: bold;
+          font-family: arial;
+      }
+  
+      .terminal-2679207537-r1 { fill: #454a50 }
+  .terminal-2679207537-r2 { fill: #e1e1e1 }
+  .terminal-2679207537-r3 { fill: #c5c8c6 }
+  .terminal-2679207537-r4 { fill: #24292f;font-weight: bold }
+  .terminal-2679207537-r5 { fill: #24292f;font-weight: bold;font-style: italic; }
+  .terminal-2679207537-r6 { fill: #000000 }
+  .terminal-2679207537-r7 { fill: #e2e3e3;font-weight: bold }
+  .terminal-2679207537-r8 { fill: #f4005f;font-weight: bold;font-style: italic; }
+  .terminal-2679207537-r9 { fill: #303336 }
+  .terminal-2679207537-r10 { fill: #a7a7a7;font-weight: bold }
+  .terminal-2679207537-r11 { fill: #620909;font-weight: bold;font-style: italic; }
+  .terminal-2679207537-r12 { fill: #0f0f0f }
+      </style>
+  
+      <defs>
+      <clipPath id="terminal-2679207537-clip-terminal">
+        <rect x="0" y="0" width="975.0" height="584.5999999999999" />
+      </clipPath>
+      <clipPath id="terminal-2679207537-line-0">
+      <rect x="0" y="1.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2679207537-line-1">
+      <rect x="0" y="25.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2679207537-line-2">
+      <rect x="0" y="50.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2679207537-line-3">
+      <rect x="0" y="74.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2679207537-line-4">
+      <rect x="0" y="99.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2679207537-line-5">
+      <rect x="0" y="123.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2679207537-line-6">
+      <rect x="0" y="147.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2679207537-line-7">
+      <rect x="0" y="172.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2679207537-line-8">
+      <rect x="0" y="196.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2679207537-line-9">
+      <rect x="0" y="221.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2679207537-line-10">
+      <rect x="0" y="245.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2679207537-line-11">
+      <rect x="0" y="269.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2679207537-line-12">
+      <rect x="0" y="294.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2679207537-line-13">
+      <rect x="0" y="318.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2679207537-line-14">
+      <rect x="0" y="343.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2679207537-line-15">
+      <rect x="0" y="367.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2679207537-line-16">
+      <rect x="0" y="391.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2679207537-line-17">
+      <rect x="0" y="416.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2679207537-line-18">
+      <rect x="0" y="440.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2679207537-line-19">
+      <rect x="0" y="465.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2679207537-line-20">
+      <rect x="0" y="489.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2679207537-line-21">
+      <rect x="0" y="513.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2679207537-line-22">
+      <rect x="0" y="538.3" width="976" height="24.65"/>
+              </clipPath>
+      </defs>
+  
+      <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="992" height="633.6" rx="8"/><text class="terminal-2679207537-title" fill="#c5c8c6" text-anchor="middle" x="496" y="27">ButtonsWithMarkupApp</text>
+              <g transform="translate(26,22)">
+              <circle cx="0" cy="0" r="7" fill="#ff5f57"/>
+              <circle cx="22" cy="0" r="7" fill="#febc2e"/>
+              <circle cx="44" cy="0" r="7" fill="#28c840"/>
+              </g>
+          
+      <g transform="translate(9, 41)" clip-path="url(#terminal-2679207537-clip-terminal)">
+      <rect fill="#24292f" x="0" y="1.5" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="195.2" y="1.5" width="780.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#e2e3e3" x="0" y="25.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#f4005f" x="12.2" y="25.9" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#e2e3e3" x="97.6" y="25.9" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#e2e3e3" x="183" y="25.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="195.2" y="25.9" width="780.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#24292f" x="0" y="50.3" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="195.2" y="50.3" width="780.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#24292f" x="0" y="74.7" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="195.2" y="74.7" width="780.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#24292f" x="0" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#24292f" x="12.2" y="99.1" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#24292f" x="97.6" y="99.1" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#24292f" x="183" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="195.2" y="99.1" width="780.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#24292f" x="0" y="123.5" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="195.2" y="123.5" width="780.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#202225" x="0" y="147.9" width="207.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="207.4" y="147.9" width="768.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#202225" x="0" y="172.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#202225" x="12.2" y="172.3" width="97.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#202225" x="109.8" y="172.3" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#202225" x="195.2" y="172.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="207.4" y="172.3" width="768.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#202225" x="0" y="196.7" width="207.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="207.4" y="196.7" width="768.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="221.1" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="245.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="269.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="294.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="318.7" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="343.1" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="367.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="391.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="416.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="440.7" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="465.1" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="489.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="513.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="538.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="562.7" width="976" height="24.65" shape-rendering="crispEdges"/>
+      <g class="terminal-2679207537-matrix">
+      <text class="terminal-2679207537-r1" x="0" y="20" textLength="195.2" clip-path="url(#terminal-2679207537-line-0)">▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔</text><text class="terminal-2679207537-r3" x="976" y="20" textLength="12.2" clip-path="url(#terminal-2679207537-line-0)">
+  </text><text class="terminal-2679207537-r5" x="12.2" y="44.4" textLength="85.4" clip-path="url(#terminal-2679207537-line-1)">Focused</text><text class="terminal-2679207537-r4" x="97.6" y="44.4" textLength="85.4" clip-path="url(#terminal-2679207537-line-1)">&#160;Button</text><text class="terminal-2679207537-r3" x="976" y="44.4" textLength="12.2" clip-path="url(#terminal-2679207537-line-1)">
+  </text><text class="terminal-2679207537-r6" x="0" y="68.8" textLength="195.2" clip-path="url(#terminal-2679207537-line-2)">▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁</text><text class="terminal-2679207537-r3" x="976" y="68.8" textLength="12.2" clip-path="url(#terminal-2679207537-line-2)">
+  </text><text class="terminal-2679207537-r1" x="0" y="93.2" textLength="195.2" clip-path="url(#terminal-2679207537-line-3)">▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔</text><text class="terminal-2679207537-r3" x="976" y="93.2" textLength="12.2" clip-path="url(#terminal-2679207537-line-3)">
+  </text><text class="terminal-2679207537-r8" x="12.2" y="117.6" textLength="85.4" clip-path="url(#terminal-2679207537-line-4)">Blurred</text><text class="terminal-2679207537-r7" x="97.6" y="117.6" textLength="85.4" clip-path="url(#terminal-2679207537-line-4)">&#160;Button</text><text class="terminal-2679207537-r3" x="976" y="117.6" textLength="12.2" clip-path="url(#terminal-2679207537-line-4)">
+  </text><text class="terminal-2679207537-r6" x="0" y="142" textLength="195.2" clip-path="url(#terminal-2679207537-line-5)">▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁</text><text class="terminal-2679207537-r3" x="976" y="142" textLength="12.2" clip-path="url(#terminal-2679207537-line-5)">
+  </text><text class="terminal-2679207537-r9" x="0" y="166.4" textLength="207.4" clip-path="url(#terminal-2679207537-line-6)">▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔</text><text class="terminal-2679207537-r3" x="976" y="166.4" textLength="12.2" clip-path="url(#terminal-2679207537-line-6)">
+  </text><text class="terminal-2679207537-r11" x="12.2" y="190.8" textLength="97.6" clip-path="url(#terminal-2679207537-line-7)">Disabled</text><text class="terminal-2679207537-r10" x="109.8" y="190.8" textLength="85.4" clip-path="url(#terminal-2679207537-line-7)">&#160;Button</text><text class="terminal-2679207537-r3" x="976" y="190.8" textLength="12.2" clip-path="url(#terminal-2679207537-line-7)">
+  </text><text class="terminal-2679207537-r12" x="0" y="215.2" textLength="207.4" clip-path="url(#terminal-2679207537-line-8)">▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁</text><text class="terminal-2679207537-r3" x="976" y="215.2" textLength="12.2" clip-path="url(#terminal-2679207537-line-8)">
+  </text><text class="terminal-2679207537-r3" x="976" y="239.6" textLength="12.2" clip-path="url(#terminal-2679207537-line-9)">
+  </text><text class="terminal-2679207537-r3" x="976" y="264" textLength="12.2" clip-path="url(#terminal-2679207537-line-10)">
+  </text><text class="terminal-2679207537-r3" x="976" y="288.4" textLength="12.2" clip-path="url(#terminal-2679207537-line-11)">
+  </text><text class="terminal-2679207537-r3" x="976" y="312.8" textLength="12.2" clip-path="url(#terminal-2679207537-line-12)">
+  </text><text class="terminal-2679207537-r3" x="976" y="337.2" textLength="12.2" clip-path="url(#terminal-2679207537-line-13)">
+  </text><text class="terminal-2679207537-r3" x="976" y="361.6" textLength="12.2" clip-path="url(#terminal-2679207537-line-14)">
+  </text><text class="terminal-2679207537-r3" x="976" y="386" textLength="12.2" clip-path="url(#terminal-2679207537-line-15)">
+  </text><text class="terminal-2679207537-r3" x="976" y="410.4" textLength="12.2" clip-path="url(#terminal-2679207537-line-16)">
+  </text><text class="terminal-2679207537-r3" x="976" y="434.8" textLength="12.2" clip-path="url(#terminal-2679207537-line-17)">
+  </text><text class="terminal-2679207537-r3" x="976" y="459.2" textLength="12.2" clip-path="url(#terminal-2679207537-line-18)">
+  </text><text class="terminal-2679207537-r3" x="976" y="483.6" textLength="12.2" clip-path="url(#terminal-2679207537-line-19)">
+  </text><text class="terminal-2679207537-r3" x="976" y="508" textLength="12.2" clip-path="url(#terminal-2679207537-line-20)">
+  </text><text class="terminal-2679207537-r3" x="976" y="532.4" textLength="12.2" clip-path="url(#terminal-2679207537-line-21)">
+  </text><text class="terminal-2679207537-r3" x="976" y="556.8" textLength="12.2" clip-path="url(#terminal-2679207537-line-22)">
+  </text>
+      </g>
+      </g>
+  </svg>
+  
+  '''
+# ---
 # name: test_buttons_render
   '''
   <svg class="rich-terminal" viewBox="0 0 994 635.5999999999999" xmlns="http://www.w3.org/2000/svg">
diff --git a/tests/snapshot_tests/snapshot_apps/button_markup.py b/tests/snapshot_tests/snapshot_apps/button_markup.py
new file mode 100644
--- /dev/null
+++ b/tests/snapshot_tests/snapshot_apps/button_markup.py
@@ -0,0 +1,14 @@
+from textual.app import App, ComposeResult
+from textual.widgets import Button
+
+
+class ButtonsWithMarkupApp(App):
+    def compose(self) -> ComposeResult:
+        yield Button("[italic red]Focused[/] Button")
+        yield Button("[italic red]Blurred[/] Button")
+        yield Button("[italic red]Disabled[/] Button", disabled=True)
+
+
+if __name__ == "__main__":
+    app = ButtonsWithMarkupApp()
+    app.run()
diff --git a/tests/snapshot_tests/test_snapshots.py b/tests/snapshot_tests/test_snapshots.py
--- a/tests/snapshot_tests/test_snapshots.py
+++ b/tests/snapshot_tests/test_snapshots.py
@@ -1170,3 +1170,7 @@ def test_button_widths(snap_compare):
 def test_welcome(snap_compare):
     assert snap_compare(SNAPSHOT_APPS_DIR / "welcome_widget.py")
 
+
+def test_button_with_console_markup(snap_compare):
+    """Regression test for https://github.com/Textualize/textual/issues/4328"""
+    assert snap_compare(SNAPSHOT_APPS_DIR / "button_markup.py")