Repository: Textualize/textual

Issue:
Some markdown component classes don't refresh on CSS update
The component classes `s`, `strong`, `em`, and `code_inline` aren't refreshed when CSS is reloaded and those are changed.
However, the component classes `markdown-table--header` and `markdown-table--lines` do refresh.

The video below shows this. Code is included after the video.

https://github.com/Textualize/textual/assets/5621605/d0f3d624-c510-4955-bfc8-5d53df590e1d

<details>
<summary>App code</summary>

```py
from textual.app import App
from textual.widgets import Markdown

MD = """
# This is a header

| col1 | col2 |
| :- | :- |
| value 1 | value 2 |

Here's some code: `from itertools import product`.
**Bold text**
_Emphasized text_
~~strikethrough~~
"""


class MyApp(App):
    CSS_PATH = "markdown.tcss"

    def compose(self):
        yield Markdown(MD)


if __name__ == "__main__":
    MyApp().run()
```

```sass
.code_inline,
.em,
.strong,
.s,
.markdown-table--header,
.markdown-table--lines,
{
    color: blue;
}
```

</details>


Discussion:
I think this is fixed. Please confirm and close the issue if that's the case.
This is still not fixed. Using the code from the first comment I'm able to reproduce the behaviour demonstrated in the screen recording.
Do you want to take a look at this? Suspect it is an easy fix.

Resolving patch:
diff --git a/src/textual/widgets/_markdown.py b/src/textual/widgets/_markdown.py
--- a/src/textual/widgets/_markdown.py
+++ b/src/textual/widgets/_markdown.py
@@ -103,6 +103,7 @@ def __init__(self, markdown: Markdown, *args, **kwargs) -> None:
         self._markdown: Markdown = markdown
         """A reference to the Markdown document that contains this block."""
         self._text = Text()
+        self._token: Token | None = None
         self._blocks: list[MarkdownBlock] = []
         super().__init__(*args, **kwargs)
 
@@ -118,6 +119,95 @@ async def action_link(self, href: str) -> None:
         """Called on link click."""
         self.post_message(Markdown.LinkClicked(self._markdown, href))
 
+    def notify_style_update(self) -> None:
+        """If CSS was reloaded, try to rebuild this block from its token."""
+        super().notify_style_update()
+        self.rebuild()
+
+    def rebuild(self) -> None:
+        """Rebuild the content of the block if we have a source token."""
+        if self._token is not None:
+            self.build_from_token(self._token)
+
+    def build_from_token(self, token: Token) -> None:
+        """Build the block content from its source token.
+
+        This method allows the block to be rebuilt on demand, which is useful
+        when the styles assigned to the
+        [Markdown.COMPONENT_CLASSES][textual.widgets.Markdown.COMPONENT_CLASSES]
+        change.
+
+        See https://github.com/Textualize/textual/issues/3464 for more information.
+
+        Args:
+            token: The token from which this block is built.
+        """
+
+        self._token = token
+        style_stack: list[Style] = [Style()]
+        content = Text()
+        if token.children:
+            for child in token.children:
+                if child.type == "text":
+                    content.append(child.content, style_stack[-1])
+                if child.type == "hardbreak":
+                    content.append("\n")
+                if child.type == "softbreak":
+                    content.append(" ", style_stack[-1])
+                elif child.type == "code_inline":
+                    content.append(
+                        child.content,
+                        style_stack[-1]
+                        + self._markdown.get_component_rich_style(
+                            "code_inline", partial=True
+                        ),
+                    )
+                elif child.type == "em_open":
+                    style_stack.append(
+                        style_stack[-1]
+                        + self._markdown.get_component_rich_style("em", partial=True)
+                    )
+                elif child.type == "strong_open":
+                    style_stack.append(
+                        style_stack[-1]
+                        + self._markdown.get_component_rich_style(
+                            "strong", partial=True
+                        )
+                    )
+                elif child.type == "s_open":
+                    style_stack.append(
+                        style_stack[-1]
+                        + self._markdown.get_component_rich_style("s", partial=True)
+                    )
+                elif child.type == "link_open":
+                    href = child.attrs.get("href", "")
+                    action = f"link({href!r})"
+                    style_stack.append(
+                        style_stack[-1] + Style.from_meta({"@click": action})
+                    )
+                elif child.type == "image":
+                    href = child.attrs.get("src", "")
+                    alt = child.attrs.get("alt", "")
+
+                    action = f"link({href!r})"
+                    style_stack.append(
+                        style_stack[-1] + Style.from_meta({"@click": action})
+                    )
+
+                    content.append("🖼  ", style_stack[-1])
+                    if alt:
+                        content.append(f"({alt})", style_stack[-1])
+                    if child.children is not None:
+                        for grandchild in child.children:
+                            content.append(grandchild.content, style_stack[-1])
+
+                    style_stack.pop()
+
+                elif child.type.endswith("_close"):
+                    style_stack.pop()
+
+        self.set_content(content)
+
 
 class MarkdownHeader(MarkdownBlock):
     """Base class for a Markdown header."""
@@ -833,67 +923,7 @@ def update(self, markdown: str) -> AwaitComplete:
                 else:
                     output.append(block)
             elif token.type == "inline":
-                style_stack: list[Style] = [Style()]
-                content = Text()
-                if token.children:
-                    for child in token.children:
-                        if child.type == "text":
-                            content.append(child.content, style_stack[-1])
-                        if child.type == "hardbreak":
-                            content.append("\n")
-                        if child.type == "softbreak":
-                            content.append(" ", style_stack[-1])
-                        elif child.type == "code_inline":
-                            content.append(
-                                child.content,
-                                style_stack[-1]
-                                + self.get_component_rich_style(
-                                    "code_inline", partial=True
-                                ),
-                            )
-                        elif child.type == "em_open":
-                            style_stack.append(
-                                style_stack[-1]
-                                + self.get_component_rich_style("em", partial=True)
-                            )
-                        elif child.type == "strong_open":
-                            style_stack.append(
-                                style_stack[-1]
-                                + self.get_component_rich_style("strong", partial=True)
-                            )
-                        elif child.type == "s_open":
-                            style_stack.append(
-                                style_stack[-1]
-                                + self.get_component_rich_style("s", partial=True)
-                            )
-                        elif child.type == "link_open":
-                            href = child.attrs.get("href", "")
-                            action = f"link({href!r})"
-                            style_stack.append(
-                                style_stack[-1] + Style.from_meta({"@click": action})
-                            )
-                        elif child.type == "image":
-                            href = child.attrs.get("src", "")
-                            alt = child.attrs.get("alt", "")
-
-                            action = f"link({href!r})"
-                            style_stack.append(
-                                style_stack[-1] + Style.from_meta({"@click": action})
-                            )
-
-                            content.append("🖼  ", style_stack[-1])
-                            if alt:
-                                content.append(f"({alt})", style_stack[-1])
-                            if child.children is not None:
-                                for grandchild in child.children:
-                                    content.append(grandchild.content, style_stack[-1])
-
-                            style_stack.pop()
-
-                        elif child.type.endswith("_close"):
-                            style_stack.pop()
-
-                stack[-1].set_content(content)
+                stack[-1].build_from_token(token)
             elif token.type in ("fence", "code_block"):
                 (stack[-1]._blocks if stack else output).append(
                     MarkdownFence(self, token.content.rstrip(), token.info)


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
@@ -22510,6 +22510,175 @@
   
   '''
 # ---
+# name: test_markdown_component_classes_reloading
+  '''
+  <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-762794446-matrix {
+          font-family: Fira Code, monospace;
+          font-size: 20px;
+          line-height: 24.4px;
+          font-variant-east-asian: full-width;
+      }
+  
+      .terminal-762794446-title {
+          font-size: 18px;
+          font-weight: bold;
+          font-family: arial;
+      }
+  
+      .terminal-762794446-r1 { fill: #e1e1e1 }
+  .terminal-762794446-r2 { fill: #121212 }
+  .terminal-762794446-r3 { fill: #c5c8c6 }
+  .terminal-762794446-r4 { fill: #0053aa }
+  .terminal-762794446-r5 { fill: #dde8f3;font-weight: bold }
+  .terminal-762794446-r6 { fill: #e2e3e3 }
+  .terminal-762794446-r7 { fill: #24292f }
+  .terminal-762794446-r8 { fill: #e2e3e3;font-weight: bold }
+  .terminal-762794446-r9 { fill: #939393;font-weight: bold }
+  .terminal-762794446-r10 { fill: #e1e1e1;font-weight: bold }
+  .terminal-762794446-r11 { fill: #e1e1e1;font-style: italic; }
+  .terminal-762794446-r12 { fill: #e1e1e1;text-decoration: line-through; }
+  .terminal-762794446-r13 { fill: #d2d2d2 }
+  .terminal-762794446-r14 { fill: #82aaff }
+  .terminal-762794446-r15 { fill: #89ddff }
+  .terminal-762794446-r16 { fill: #c3e88d }
+      </style>
+  
+      <defs>
+      <clipPath id="terminal-762794446-clip-terminal">
+        <rect x="0" y="0" width="975.0" height="584.5999999999999" />
+      </clipPath>
+      <clipPath id="terminal-762794446-line-0">
+      <rect x="0" y="1.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-762794446-line-1">
+      <rect x="0" y="25.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-762794446-line-2">
+      <rect x="0" y="50.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-762794446-line-3">
+      <rect x="0" y="74.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-762794446-line-4">
+      <rect x="0" y="99.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-762794446-line-5">
+      <rect x="0" y="123.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-762794446-line-6">
+      <rect x="0" y="147.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-762794446-line-7">
+      <rect x="0" y="172.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-762794446-line-8">
+      <rect x="0" y="196.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-762794446-line-9">
+      <rect x="0" y="221.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-762794446-line-10">
+      <rect x="0" y="245.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-762794446-line-11">
+      <rect x="0" y="269.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-762794446-line-12">
+      <rect x="0" y="294.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-762794446-line-13">
+      <rect x="0" y="318.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-762794446-line-14">
+      <rect x="0" y="343.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-762794446-line-15">
+      <rect x="0" y="367.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-762794446-line-16">
+      <rect x="0" y="391.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-762794446-line-17">
+      <rect x="0" y="416.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-762794446-line-18">
+      <rect x="0" y="440.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-762794446-line-19">
+      <rect x="0" y="465.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-762794446-line-20">
+      <rect x="0" y="489.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-762794446-line-21">
+      <rect x="0" y="513.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-762794446-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-762794446-title" fill="#c5c8c6" text-anchor="middle" x="496" y="27">MyApp</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-762794446-clip-terminal)">
+      <rect fill="#1e1e1e" x="0" y="1.5" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="48.8" y="1.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="61" y="1.5" width="854" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="915" y="1.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="927.2" y="1.5" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="25.9" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#0053aa" x="48.8" y="25.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#0053aa" x="61" y="25.9" width="854" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="915" y="25.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="927.2" y="25.9" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="50.3" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#0053aa" x="48.8" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#0053aa" x="61" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#0053aa" x="73.2" y="50.3" width="317.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#0053aa" x="390.4" y="50.3" width="122" height="24.65" shape-rendering="crispEdges"/><rect fill="#0053aa" x="512.4" y="50.3" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#0053aa" x="585.6" y="50.3" width="317.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#0053aa" x="902.8" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="915" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="927.2" y="50.3" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="74.7" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#0053aa" x="48.8" y="74.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#0053aa" x="61" y="74.7" width="854" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="915" y="74.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="927.2" y="74.7" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="99.1" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="48.8" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="61" y="99.1" width="854" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="915" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="927.2" y="99.1" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="123.5" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="48.8" y="123.5" width="878.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="927.2" y="123.5" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="147.9" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="48.8" y="147.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="61" y="147.9" width="854" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="915" y="147.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="927.2" y="147.9" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="172.3" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#24292f" x="48.8" y="172.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#24292f" x="61" y="172.3" width="854" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="915" y="172.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="927.2" y="172.3" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="196.7" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#24292f" x="48.8" y="196.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#24292f" x="61" y="196.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#24292f" x="73.2" y="196.7" width="414.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#24292f" x="488" y="196.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#24292f" x="500.2" y="196.7" width="402.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#24292f" x="902.8" y="196.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="915" y="196.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="927.2" y="196.7" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="221.1" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#24292f" x="48.8" y="221.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#24292f" x="61" y="221.1" width="854" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="915" y="221.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="927.2" y="221.1" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="245.5" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#24292f" x="48.8" y="245.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#24292f" x="61" y="245.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#24292f" x="73.2" y="245.5" width="414.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#24292f" x="488" y="245.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#24292f" x="500.2" y="245.5" width="402.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#24292f" x="902.8" y="245.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="915" y="245.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="927.2" y="245.5" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="269.9" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#24292f" x="48.8" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#24292f" x="61" y="269.9" width="854" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="915" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="927.2" y="269.9" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="294.3" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="48.8" y="294.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="61" y="294.3" width="854" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="915" y="294.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="927.2" y="294.3" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="318.7" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="48.8" y="318.7" width="878.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="927.2" y="318.7" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="343.1" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="48.8" y="343.1" width="219.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="268.4" y="343.1" width="353.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="622.2" y="343.1" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="343.1" width="109.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="756.4" y="343.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="768.6" y="343.1" width="134.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="902.8" y="343.1" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="927.2" y="343.1" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="367.5" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="48.8" y="367.5" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="97.6" y="367.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="109.8" y="367.5" width="158.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="268.4" y="367.5" width="658.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="927.2" y="367.5" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="391.9" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="48.8" y="391.9" width="878.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="927.2" y="391.9" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="416.3" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#263238" x="48.8" y="416.3" width="878.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="927.2" y="416.3" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="440.7" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#263238" x="48.8" y="440.7" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#263238" x="73.2" y="440.7" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#263238" x="134.2" y="440.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#263238" x="146.4" y="440.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#263238" x="158.6" y="440.7" width="158.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#263238" x="317.2" y="440.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#263238" x="329.4" y="440.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#263238" x="341.6" y="440.7" width="561.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#263238" x="902.8" y="440.7" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="927.2" y="440.7" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="465.1" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#263238" x="48.8" y="465.1" width="878.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="927.2" y="465.1" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="489.5" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="48.8" y="489.5" width="878.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="927.2" y="489.5" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="513.9" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="48.8" y="513.9" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="97.6" y="513.9" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="158.6" y="513.9" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="207.4" y="513.9" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="280.6" y="513.9" width="646.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="927.2" y="513.9" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="538.3" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="48.8" y="538.3" width="878.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="927.2" y="538.3" width="48.8" 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-762794446-matrix">
+      <text class="terminal-762794446-r2" x="48.8" y="20" textLength="12.2" clip-path="url(#terminal-762794446-line-0)">▁</text><text class="terminal-762794446-r2" x="61" y="20" textLength="854" clip-path="url(#terminal-762794446-line-0)">▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁</text><text class="terminal-762794446-r2" x="915" y="20" textLength="12.2" clip-path="url(#terminal-762794446-line-0)">▁</text><text class="terminal-762794446-r3" x="976" y="20" textLength="12.2" clip-path="url(#terminal-762794446-line-0)">
+  </text><text class="terminal-762794446-r2" x="48.8" y="44.4" textLength="12.2" clip-path="url(#terminal-762794446-line-1)">▎</text><text class="terminal-762794446-r4" x="915" y="44.4" textLength="12.2" clip-path="url(#terminal-762794446-line-1)">▊</text><text class="terminal-762794446-r3" x="976" y="44.4" textLength="12.2" clip-path="url(#terminal-762794446-line-1)">
+  </text><text class="terminal-762794446-r2" x="48.8" y="68.8" textLength="12.2" clip-path="url(#terminal-762794446-line-2)">▎</text><text class="terminal-762794446-r5" x="390.4" y="68.8" textLength="122" clip-path="url(#terminal-762794446-line-2)">This&#160;is&#160;a&#160;</text><text class="terminal-762794446-r5" x="512.4" y="68.8" textLength="73.2" clip-path="url(#terminal-762794446-line-2)">header</text><text class="terminal-762794446-r4" x="915" y="68.8" textLength="12.2" clip-path="url(#terminal-762794446-line-2)">▊</text><text class="terminal-762794446-r3" x="976" y="68.8" textLength="12.2" clip-path="url(#terminal-762794446-line-2)">
+  </text><text class="terminal-762794446-r2" x="48.8" y="93.2" textLength="12.2" clip-path="url(#terminal-762794446-line-3)">▎</text><text class="terminal-762794446-r4" x="915" y="93.2" textLength="12.2" clip-path="url(#terminal-762794446-line-3)">▊</text><text class="terminal-762794446-r3" x="976" y="93.2" textLength="12.2" clip-path="url(#terminal-762794446-line-3)">
+  </text><text class="terminal-762794446-r2" x="48.8" y="117.6" textLength="12.2" clip-path="url(#terminal-762794446-line-4)">▔</text><text class="terminal-762794446-r2" x="61" y="117.6" textLength="854" clip-path="url(#terminal-762794446-line-4)">▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔</text><text class="terminal-762794446-r2" x="915" y="117.6" textLength="12.2" clip-path="url(#terminal-762794446-line-4)">▔</text><text class="terminal-762794446-r3" x="976" y="117.6" textLength="12.2" clip-path="url(#terminal-762794446-line-4)">
+  </text><text class="terminal-762794446-r3" x="976" y="142" textLength="12.2" clip-path="url(#terminal-762794446-line-5)">
+  </text><text class="terminal-762794446-r2" x="48.8" y="166.4" textLength="12.2" clip-path="url(#terminal-762794446-line-6)">▁</text><text class="terminal-762794446-r2" x="61" y="166.4" textLength="854" clip-path="url(#terminal-762794446-line-6)">▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁</text><text class="terminal-762794446-r2" x="915" y="166.4" textLength="12.2" clip-path="url(#terminal-762794446-line-6)">▁</text><text class="terminal-762794446-r3" x="976" y="166.4" textLength="12.2" clip-path="url(#terminal-762794446-line-6)">
+  </text><text class="terminal-762794446-r2" x="48.8" y="190.8" textLength="12.2" clip-path="url(#terminal-762794446-line-7)">▎</text><text class="terminal-762794446-r7" x="915" y="190.8" textLength="12.2" clip-path="url(#terminal-762794446-line-7)">▊</text><text class="terminal-762794446-r3" x="976" y="190.8" textLength="12.2" clip-path="url(#terminal-762794446-line-7)">
+  </text><text class="terminal-762794446-r2" x="48.8" y="215.2" textLength="12.2" clip-path="url(#terminal-762794446-line-8)">▎</text><text class="terminal-762794446-r8" x="73.2" y="215.2" textLength="414.8" clip-path="url(#terminal-762794446-line-8)">col1&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-762794446-r8" x="500.2" y="215.2" textLength="402.6" clip-path="url(#terminal-762794446-line-8)">col2&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-762794446-r7" x="915" y="215.2" textLength="12.2" clip-path="url(#terminal-762794446-line-8)">▊</text><text class="terminal-762794446-r3" x="976" y="215.2" textLength="12.2" clip-path="url(#terminal-762794446-line-8)">
+  </text><text class="terminal-762794446-r2" x="48.8" y="239.6" textLength="12.2" clip-path="url(#terminal-762794446-line-9)">▎</text><text class="terminal-762794446-r6" x="61" y="239.6" textLength="854" clip-path="url(#terminal-762794446-line-9)">&#160;━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━&#160;</text><text class="terminal-762794446-r7" x="915" y="239.6" textLength="12.2" clip-path="url(#terminal-762794446-line-9)">▊</text><text class="terminal-762794446-r3" x="976" y="239.6" textLength="12.2" clip-path="url(#terminal-762794446-line-9)">
+  </text><text class="terminal-762794446-r2" x="48.8" y="264" textLength="12.2" clip-path="url(#terminal-762794446-line-10)">▎</text><text class="terminal-762794446-r6" x="73.2" y="264" textLength="414.8" clip-path="url(#terminal-762794446-line-10)">value&#160;1&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-762794446-r6" x="500.2" y="264" textLength="402.6" clip-path="url(#terminal-762794446-line-10)">value&#160;2&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-762794446-r7" x="915" y="264" textLength="12.2" clip-path="url(#terminal-762794446-line-10)">▊</text><text class="terminal-762794446-r3" x="976" y="264" textLength="12.2" clip-path="url(#terminal-762794446-line-10)">
+  </text><text class="terminal-762794446-r2" x="48.8" y="288.4" textLength="12.2" clip-path="url(#terminal-762794446-line-11)">▎</text><text class="terminal-762794446-r7" x="915" y="288.4" textLength="12.2" clip-path="url(#terminal-762794446-line-11)">▊</text><text class="terminal-762794446-r3" x="976" y="288.4" textLength="12.2" clip-path="url(#terminal-762794446-line-11)">
+  </text><text class="terminal-762794446-r2" x="48.8" y="312.8" textLength="12.2" clip-path="url(#terminal-762794446-line-12)">▔</text><text class="terminal-762794446-r2" x="61" y="312.8" textLength="854" clip-path="url(#terminal-762794446-line-12)">▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔</text><text class="terminal-762794446-r2" x="915" y="312.8" textLength="12.2" clip-path="url(#terminal-762794446-line-12)">▔</text><text class="terminal-762794446-r3" x="976" y="312.8" textLength="12.2" clip-path="url(#terminal-762794446-line-12)">
+  </text><text class="terminal-762794446-r3" x="976" y="337.2" textLength="12.2" clip-path="url(#terminal-762794446-line-13)">
+  </text><text class="terminal-762794446-r1" x="48.8" y="361.6" textLength="219.6" clip-path="url(#terminal-762794446-line-14)">Here&#x27;s&#160;some&#160;code:&#160;</text><text class="terminal-762794446-r9" x="268.4" y="361.6" textLength="353.8" clip-path="url(#terminal-762794446-line-14)">from&#160;itertools&#160;import&#160;product</text><text class="terminal-762794446-r1" x="622.2" y="361.6" textLength="24.4" clip-path="url(#terminal-762794446-line-14)">.&#160;</text><text class="terminal-762794446-r10" x="646.6" y="361.6" textLength="109.8" clip-path="url(#terminal-762794446-line-14)">Bold&#160;text</text><text class="terminal-762794446-r11" x="768.6" y="361.6" textLength="134.2" clip-path="url(#terminal-762794446-line-14)">Emphasized&#160;</text><text class="terminal-762794446-r3" x="976" y="361.6" textLength="12.2" clip-path="url(#terminal-762794446-line-14)">
+  </text><text class="terminal-762794446-r11" x="48.8" y="386" textLength="48.8" clip-path="url(#terminal-762794446-line-15)">text</text><text class="terminal-762794446-r12" x="109.8" y="386" textLength="158.6" clip-path="url(#terminal-762794446-line-15)">strikethrough</text><text class="terminal-762794446-r3" x="976" y="386" textLength="12.2" clip-path="url(#terminal-762794446-line-15)">
+  </text><text class="terminal-762794446-r3" x="976" y="410.4" textLength="12.2" clip-path="url(#terminal-762794446-line-16)">
+  </text><text class="terminal-762794446-r3" x="976" y="434.8" textLength="12.2" clip-path="url(#terminal-762794446-line-17)">
+  </text><text class="terminal-762794446-r14" x="73.2" y="459.2" textLength="61" clip-path="url(#terminal-762794446-line-18)">print</text><text class="terminal-762794446-r15" x="134.2" y="459.2" textLength="12.2" clip-path="url(#terminal-762794446-line-18)">(</text><text class="terminal-762794446-r16" x="146.4" y="459.2" textLength="12.2" clip-path="url(#terminal-762794446-line-18)">&quot;</text><text class="terminal-762794446-r16" x="158.6" y="459.2" textLength="158.6" clip-path="url(#terminal-762794446-line-18)">Hello,&#160;world!</text><text class="terminal-762794446-r16" x="317.2" y="459.2" textLength="12.2" clip-path="url(#terminal-762794446-line-18)">&quot;</text><text class="terminal-762794446-r15" x="329.4" y="459.2" textLength="12.2" clip-path="url(#terminal-762794446-line-18)">)</text><text class="terminal-762794446-r3" x="976" y="459.2" textLength="12.2" clip-path="url(#terminal-762794446-line-18)">
+  </text><text class="terminal-762794446-r3" x="976" y="483.6" textLength="12.2" clip-path="url(#terminal-762794446-line-19)">
+  </text><text class="terminal-762794446-r3" x="976" y="508" textLength="12.2" clip-path="url(#terminal-762794446-line-20)">
+  </text><text class="terminal-762794446-r10" x="48.8" y="532.4" textLength="48.8" clip-path="url(#terminal-762794446-line-21)">That</text><text class="terminal-762794446-r1" x="97.6" y="532.4" textLength="61" clip-path="url(#terminal-762794446-line-21)">&#160;was&#160;</text><text class="terminal-762794446-r11" x="158.6" y="532.4" textLength="48.8" clip-path="url(#terminal-762794446-line-21)">some</text><text class="terminal-762794446-r1" x="207.4" y="532.4" textLength="73.2" clip-path="url(#terminal-762794446-line-21)">&#160;code.</text><text class="terminal-762794446-r3" x="976" y="532.4" textLength="12.2" clip-path="url(#terminal-762794446-line-21)">
+  </text><text class="terminal-762794446-r3" x="976" y="556.8" textLength="12.2" clip-path="url(#terminal-762794446-line-22)">
+  </text>
+      </g>
+      </g>
+  </svg>
+  
+  '''
+# ---
 # name: test_markdown_dark_theme_override
   '''
   <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/markdown_component_classes_reloading.py b/tests/snapshot_tests/snapshot_apps/markdown_component_classes_reloading.py
new file mode 100644
--- /dev/null
+++ b/tests/snapshot_tests/snapshot_apps/markdown_component_classes_reloading.py
@@ -0,0 +1,50 @@
+from pathlib import Path
+
+from textual.app import App, ComposeResult
+from textual.widgets import Markdown
+
+CSS_PATH = (Path(__file__) / "../markdown_component_classes_reloading.tcss").resolve()
+
+CSS_PATH.write_text(
+    """\
+.code_inline,
+.em,
+.strong,
+.s,
+.markdown-table--header,
+.markdown-table--lines,
+{
+    color: yellow;
+}
+"""
+)
+
+MD = """
+# This is a **header**
+
+| col1 | col2 |
+| :- | :- |
+| value 1 | value 2 |
+
+Here's some code: `from itertools import product`.
+**Bold text**
+_Emphasized text_
+~~strikethrough~~
+
+```py
+print("Hello, world!")
+```
+
+**That** was _some_ code.
+"""
+
+
+class MyApp(App[None]):
+    CSS_PATH = CSS_PATH
+
+    def compose(self) -> ComposeResult:
+        yield Markdown(MD)
+
+
+if __name__ == "__main__":
+    MyApp().run()
diff --git a/tests/snapshot_tests/snapshot_apps/markdown_component_classes_reloading.tcss b/tests/snapshot_tests/snapshot_apps/markdown_component_classes_reloading.tcss
new file mode 100644
--- /dev/null
+++ b/tests/snapshot_tests/snapshot_apps/markdown_component_classes_reloading.tcss
@@ -0,0 +1 @@
+/* This file is purposefully empty. */
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
@@ -650,6 +650,27 @@ async def run_before(pilot):
     )
 
 
+def test_markdown_component_classes_reloading(snap_compare, monkeypatch):
+    """Tests all markdown component classes reload correctly.
+
+    See https://github.com/Textualize/textual/issues/3464."""
+
+    monkeypatch.setenv(
+        "TEXTUAL", "debug"
+    )  # This will make sure we create a file monitor.
+
+    async def run_before(pilot):
+        css_file = pilot.app.CSS_PATH
+        with open(css_file, "w") as f:
+            f.write("/* This file is purposefully empty. */\n")  # Clear all the CSS.
+        await pilot.app._on_css_change()
+
+    assert snap_compare(
+        SNAPSHOT_APPS_DIR / "markdown_component_classes_reloading.py",
+        run_before=run_before,
+    )
+
+
 def test_layer_fix(snap_compare):
     # Check https://github.com/Textualize/textual/issues/1358
     assert snap_compare(SNAPSHOT_APPS_DIR / "layer_fix.py", press=["d"])