Repository: Textualize/rich

Issue:
[BUG] If `show_header=False` the next row will have the header border style
![img](https://i.kek.sh/KBeQ0CkeCOj.png)


Discussion:
Thanks for the report, I've reproduced this myself
This is an artefact of how Rich stores box information. A solution is to set `box=box.SQUARE` when you want a header.
I was thinking we could have Rich default to box=None, then in the constructor do something like:

```python
if box is None:
    if show_header:
        self.box = box.HEAVY_HEAD
    else:
        self.box = box.SQUARE
else:
    self.box = box
```

It seems that would give the user what they would expect in this case, yet still allow for the same level of customisation when required.
There may be other box constants that don't look quite right with no header. I'd prefer something codified in to box itself so there is isn't this special case.

Resolving patch:
diff --git a/rich/box.py b/rich/box.py
--- a/rich/box.py
+++ b/rich/box.py
@@ -88,6 +88,16 @@ def substitute(self, options: "ConsoleOptions", safe: bool = True) -> "Box":
             box = ASCII
         return box
 
+    def get_plain_headed_box(self) -> "Box":
+        """If this box uses special characters for the borders of the header, then
+        return the equivalent box that does not.
+
+        Returns:
+            Box: The most similar Box that doesn't use header-specific box characters.
+                If the current Box already satisfies this criterion, then it's returned.
+        """
+        return PLAIN_HEADED_SUBSTITUTIONS.get(self, self)
+
     def get_top(self, widths: Iterable[int]) -> str:
         """Get the top of a simple box.
 
@@ -429,6 +439,15 @@ def get_bottom(self, widths: Iterable[int]) -> str:
     HEAVY_HEAD: SQUARE,
 }
 
+# Map headed boxes to their headerless equivalents
+PLAIN_HEADED_SUBSTITUTIONS = {
+    HEAVY_HEAD: SQUARE,
+    SQUARE_DOUBLE_HEAD: SQUARE,
+    MINIMAL_DOUBLE_HEAD: MINIMAL,
+    MINIMAL_HEAVY_HEAD: MINIMAL,
+    ASCII_DOUBLE_HEAD: ASCII2,
+}
+
 
 if __name__ == "__main__":  # pragma: no cover
 
diff --git a/rich/table.py b/rich/table.py
--- a/rich/table.py
+++ b/rich/table.py
@@ -752,8 +752,8 @@ def _render(
             if self.box
             else None
         )
+        _box = _box.get_plain_headed_box() if _box and not self.show_header else _box
 
-        # _box = self.box
         new_line = Segment.line()
 
         columns = self.columns


Test patch:
diff --git a/tests/test_table.py b/tests/test_table.py
--- a/tests/test_table.py
+++ b/tests/test_table.py
@@ -181,6 +181,34 @@ def make_table(vertical_align):
     assert result == expected
 
 
+@pytest.mark.parametrize(
+    "box,result",
+    [
+        (None, " 1  2 \n 3  4 \n"),
+        (box.HEAVY_HEAD, "┌───┬───┐\n│ 1 │ 2 │\n│ 3 │ 4 │\n└───┴───┘\n"),
+        (box.SQUARE_DOUBLE_HEAD, "┌───┬───┐\n│ 1 │ 2 │\n│ 3 │ 4 │\n└───┴───┘\n"),
+        (box.MINIMAL_DOUBLE_HEAD, "    ╷    \n  1 │ 2  \n  3 │ 4  \n    ╵    \n"),
+        (box.MINIMAL_HEAVY_HEAD, "    ╷    \n  1 │ 2  \n  3 │ 4  \n    ╵    \n"),
+        (box.ASCII_DOUBLE_HEAD, "+---+---+\n| 1 | 2 |\n| 3 | 4 |\n+---+---+\n"),
+    ],
+)
+def test_table_show_header_false_substitution(box, result):
+    """When the box style is one with a custom header edge, it should be substituted for
+    the equivalent box that does not have a custom header when show_header=False"""
+    table = Table(show_header=False, box=box)
+    table.add_column()
+    table.add_column()
+
+    table.add_row("1", "2")
+    table.add_row("3", "4")
+
+    console = Console(record=True)
+    console.print(table)
+    output = console.export_text()
+
+    assert output == result
+
+
 if __name__ == "__main__":
     render = render_tables()
     print(render)