Repository: spectreconsole/spectre.console

Issue:
Add option to show separators between rows
**Is your feature request related to a problem? Please describe.**
It would be nice if there was a way to show separators between table rows.

**Additional context**
This should be opt-in


Hints:

Patch:
diff --git a/examples/Console/Borders/Program.cs b/examples/Console/Borders/Program.cs
index 0dca10135..2b755ae82 100644
--- a/examples/Console/Borders/Program.cs
+++ b/examples/Console/Borders/Program.cs
@@ -20,21 +20,22 @@ private static void PanelBorders()
     {
         static IRenderable CreatePanel(string name, BoxBorder border)
         {
-            return new Panel($"This is a panel with\nthe [yellow]{name}[/] border.")
-                .Header($" [blue]{name}[/] ", Justify.Center)
-                .Border(border)
-                .BorderStyle(Style.Parse("grey"));
+            return
+                new Panel($"This is a panel with\nthe [yellow]{name}[/] border.")
+                    .Header($" [blue]{name}[/] ", Justify.Center)
+                    .Border(border)
+                    .BorderStyle(Style.Parse("grey"));
         }
 
         var items = new[]
         {
-                CreatePanel("Ascii", BoxBorder.Ascii),
-                CreatePanel("Square", BoxBorder.Square),
-                CreatePanel("Rounded", BoxBorder.Rounded),
-                CreatePanel("Heavy", BoxBorder.Heavy),
-                CreatePanel("Double", BoxBorder.Double),
-                CreatePanel("None", BoxBorder.None),
-            };
+            CreatePanel("Ascii", BoxBorder.Ascii),
+            CreatePanel("Square", BoxBorder.Square),
+            CreatePanel("Rounded", BoxBorder.Rounded),
+            CreatePanel("Heavy", BoxBorder.Heavy),
+            CreatePanel("Double", BoxBorder.Double),
+            CreatePanel("None", BoxBorder.None),
+        };
 
         AnsiConsole.Write(
             new Padder(
@@ -47,6 +48,7 @@ private static void TableBorders()
         static IRenderable CreateTable(string name, TableBorder border)
         {
             var table = new Table().Border(border);
+            table.ShowRowSeparators();
             table.AddColumn("[yellow]Header 1[/]", c => c.Footer("[grey]Footer 1[/]"));
             table.AddColumn("[yellow]Header 2[/]", col => col.Footer("[grey]Footer 2[/]").RightAligned());
             table.AddRow("Cell", "Cell");
@@ -54,29 +56,23 @@ static IRenderable CreateTable(string name, TableBorder border)
 
             return new Panel(table)
                 .Header($" [blue]{name}[/] ", Justify.Center)
+                .PadBottom(1)
                 .NoBorder();
         }
 
         var items = new[]
         {
-                CreateTable("Ascii", TableBorder.Ascii),
-                CreateTable("Ascii2", TableBorder.Ascii2),
-                CreateTable("AsciiDoubleHead", TableBorder.AsciiDoubleHead),
-                CreateTable("Horizontal", TableBorder.Horizontal),
-                CreateTable("Simple", TableBorder.Simple),
-                CreateTable("SimpleHeavy", TableBorder.SimpleHeavy),
-                CreateTable("Minimal", TableBorder.Minimal),
-                CreateTable("MinimalHeavyHead", TableBorder.MinimalHeavyHead),
-                CreateTable("MinimalDoubleHead", TableBorder.MinimalDoubleHead),
-                CreateTable("Square", TableBorder.Square),
-                CreateTable("Rounded", TableBorder.Rounded),
-                CreateTable("Heavy", TableBorder.Heavy),
-                CreateTable("HeavyEdge", TableBorder.HeavyEdge),
-                CreateTable("HeavyHead", TableBorder.HeavyHead),
-                CreateTable("Double", TableBorder.Double),
-                CreateTable("DoubleEdge", TableBorder.DoubleEdge),
-                CreateTable("Markdown", TableBorder.Markdown),
-            };
+            CreateTable("Ascii", TableBorder.Ascii), CreateTable("Ascii2", TableBorder.Ascii2),
+            CreateTable("AsciiDoubleHead", TableBorder.AsciiDoubleHead),
+            CreateTable("Horizontal", TableBorder.Horizontal), CreateTable("Simple", TableBorder.Simple),
+            CreateTable("SimpleHeavy", TableBorder.SimpleHeavy), CreateTable("Minimal", TableBorder.Minimal),
+            CreateTable("MinimalHeavyHead", TableBorder.MinimalHeavyHead),
+            CreateTable("MinimalDoubleHead", TableBorder.MinimalDoubleHead),
+            CreateTable("Square", TableBorder.Square), CreateTable("Rounded", TableBorder.Rounded),
+            CreateTable("Heavy", TableBorder.Heavy), CreateTable("HeavyEdge", TableBorder.HeavyEdge),
+            CreateTable("HeavyHead", TableBorder.HeavyHead), CreateTable("Double", TableBorder.Double),
+            CreateTable("DoubleEdge", TableBorder.DoubleEdge), CreateTable("Markdown", TableBorder.Markdown),
+        };
 
         AnsiConsole.Write(new Columns(items).Collapse());
     }
@@ -87,4 +83,4 @@ private static void HorizontalRule(string title)
         AnsiConsole.Write(new Rule($"[white bold]{title}[/]").RuleStyle("grey").LeftJustified());
         AnsiConsole.WriteLine();
     }
-}
+}
\ No newline at end of file
diff --git a/src/Spectre.Console/Extensions/TableExtensions.cs b/src/Spectre.Console/Extensions/TableExtensions.cs
index e232e64bc..ddb23315a 100644
--- a/src/Spectre.Console/Extensions/TableExtensions.cs
+++ b/src/Spectre.Console/Extensions/TableExtensions.cs
@@ -334,6 +334,38 @@ public static Table HideHeaders(this Table table)
         return table;
     }
 
+    /// <summary>
+    /// Shows row separators.
+    /// </summary>
+    /// <param name="table">The table.</param>
+    /// <returns>The same instance so that multiple calls can be chained.</returns>
+    public static Table ShowRowSeparators(this Table table)
+    {
+        if (table is null)
+        {
+            throw new ArgumentNullException(nameof(table));
+        }
+
+        table.ShowRowSeparators = true;
+        return table;
+    }
+
+    /// <summary>
+    /// Hides row separators.
+    /// </summary>
+    /// <param name="table">The table.</param>
+    /// <returns>The same instance so that multiple calls can be chained.</returns>
+    public static Table HideRowSeparators(this Table table)
+    {
+        if (table is null)
+        {
+            throw new ArgumentNullException(nameof(table));
+        }
+
+        table.ShowRowSeparators = false;
+        return table;
+    }
+
     /// <summary>
     /// Shows table footers.
     /// </summary>
diff --git a/src/Spectre.Console/Rendering/Borders/TableBorderPart.cs b/src/Spectre.Console/Rendering/Borders/TableBorderPart.cs
index 2359266a8..dbf64af66 100644
--- a/src/Spectre.Console/Rendering/Borders/TableBorderPart.cs
+++ b/src/Spectre.Console/Rendering/Borders/TableBorderPart.cs
@@ -114,4 +114,24 @@ public enum TableBorderPart
     /// The bottom right part of a footer.
     /// </summary>
     FooterBottomRight,
+
+    /// <summary>
+    /// The left part of a row.
+    /// </summary>
+    RowLeft,
+
+    /// <summary>
+    /// The center part of a row.
+    /// </summary>
+    RowCenter,
+
+    /// <summary>
+    /// The separator part of a row.
+    /// </summary>
+    RowSeparator,
+
+    /// <summary>
+    /// The right part of a row.
+    /// </summary>
+    RowRight,
 }
\ No newline at end of file
diff --git a/src/Spectre.Console/Rendering/Borders/Tables/Ascii2TableBorder.cs b/src/Spectre.Console/Rendering/Borders/Tables/Ascii2TableBorder.cs
index 7bd17d60e..0cd6122ce 100644
--- a/src/Spectre.Console/Rendering/Borders/Tables/Ascii2TableBorder.cs
+++ b/src/Spectre.Console/Rendering/Borders/Tables/Ascii2TableBorder.cs
@@ -32,6 +32,10 @@ public override string GetPart(TableBorderPart part)
             TableBorderPart.FooterBottom => "-",
             TableBorderPart.FooterBottomSeparator => "+",
             TableBorderPart.FooterBottomRight => "+",
+            TableBorderPart.RowLeft => "|",
+            TableBorderPart.RowCenter => "-",
+            TableBorderPart.RowSeparator => "+",
+            TableBorderPart.RowRight => "|",
             _ => throw new InvalidOperationException("Unknown border part."),
         };
     }
diff --git a/src/Spectre.Console/Rendering/Borders/Tables/AsciiDoubleHeadTableBorder.cs b/src/Spectre.Console/Rendering/Borders/Tables/AsciiDoubleHeadTableBorder.cs
index f5f2bad6d..33c78ce88 100644
--- a/src/Spectre.Console/Rendering/Borders/Tables/AsciiDoubleHeadTableBorder.cs
+++ b/src/Spectre.Console/Rendering/Borders/Tables/AsciiDoubleHeadTableBorder.cs
@@ -32,6 +32,10 @@ public override string GetPart(TableBorderPart part)
             TableBorderPart.FooterBottom => "-",
             TableBorderPart.FooterBottomSeparator => "+",
             TableBorderPart.FooterBottomRight => "+",
+            TableBorderPart.RowLeft => "|",
+            TableBorderPart.RowCenter => "-",
+            TableBorderPart.RowSeparator => "+",
+            TableBorderPart.RowRight => "|",
             _ => throw new InvalidOperationException("Unknown border part."),
         };
     }
diff --git a/src/Spectre.Console/Rendering/Borders/Tables/AsciiTableBorder.cs b/src/Spectre.Console/Rendering/Borders/Tables/AsciiTableBorder.cs
index 7f4f743b5..903665c5e 100644
--- a/src/Spectre.Console/Rendering/Borders/Tables/AsciiTableBorder.cs
+++ b/src/Spectre.Console/Rendering/Borders/Tables/AsciiTableBorder.cs
@@ -32,6 +32,10 @@ public override string GetPart(TableBorderPart part)
             TableBorderPart.FooterBottom => "-",
             TableBorderPart.FooterBottomSeparator => "-",
             TableBorderPart.FooterBottomRight => "+",
+            TableBorderPart.RowLeft => "|",
+            TableBorderPart.RowCenter => "-",
+            TableBorderPart.RowSeparator => "+",
+            TableBorderPart.RowRight => "|",
             _ => throw new InvalidOperationException("Unknown border part."),
         };
     }
diff --git a/src/Spectre.Console/Rendering/Borders/Tables/DoubleEdgeTableBorder.cs b/src/Spectre.Console/Rendering/Borders/Tables/DoubleEdgeTableBorder.cs
index ab1362127..6f756ff74 100644
--- a/src/Spectre.Console/Rendering/Borders/Tables/DoubleEdgeTableBorder.cs
+++ b/src/Spectre.Console/Rendering/Borders/Tables/DoubleEdgeTableBorder.cs
@@ -32,6 +32,10 @@ public override string GetPart(TableBorderPart part)
             TableBorderPart.FooterBottom => "═",
             TableBorderPart.FooterBottomSeparator => "╧",
             TableBorderPart.FooterBottomRight => "╝",
+            TableBorderPart.RowLeft => "╟",
+            TableBorderPart.RowCenter => "─",
+            TableBorderPart.RowSeparator => "┼",
+            TableBorderPart.RowRight => "╢",
             _ => throw new InvalidOperationException("Unknown border part."),
         };
     }
diff --git a/src/Spectre.Console/Rendering/Borders/Tables/DoubleTableBorder.cs b/src/Spectre.Console/Rendering/Borders/Tables/DoubleTableBorder.cs
index b5eca04cd..40b2f6b72 100644
--- a/src/Spectre.Console/Rendering/Borders/Tables/DoubleTableBorder.cs
+++ b/src/Spectre.Console/Rendering/Borders/Tables/DoubleTableBorder.cs
@@ -32,6 +32,10 @@ public override string GetPart(TableBorderPart part)
             TableBorderPart.FooterBottom => "═",
             TableBorderPart.FooterBottomSeparator => "╩",
             TableBorderPart.FooterBottomRight => "╝",
+            TableBorderPart.RowLeft => "╠",
+            TableBorderPart.RowCenter => "═",
+            TableBorderPart.RowSeparator => "╬",
+            TableBorderPart.RowRight => "╣",
             _ => throw new InvalidOperationException("Unknown border part."),
         };
     }
diff --git a/src/Spectre.Console/Rendering/Borders/Tables/HeavyEdgeTableBorder.cs b/src/Spectre.Console/Rendering/Borders/Tables/HeavyEdgeTableBorder.cs
index f5728be27..31cadcab4 100644
--- a/src/Spectre.Console/Rendering/Borders/Tables/HeavyEdgeTableBorder.cs
+++ b/src/Spectre.Console/Rendering/Borders/Tables/HeavyEdgeTableBorder.cs
@@ -35,6 +35,10 @@ public override string GetPart(TableBorderPart part)
             TableBorderPart.FooterBottom => "━",
             TableBorderPart.FooterBottomSeparator => "┷",
             TableBorderPart.FooterBottomRight => "┛",
+            TableBorderPart.RowLeft => "┠",
+            TableBorderPart.RowCenter => "─",
+            TableBorderPart.RowSeparator => "┼",
+            TableBorderPart.RowRight => "┨",
             _ => throw new InvalidOperationException("Unknown border part."),
         };
     }
diff --git a/src/Spectre.Console/Rendering/Borders/Tables/HeavyHeadTableBorder.cs b/src/Spectre.Console/Rendering/Borders/Tables/HeavyHeadTableBorder.cs
index 87f357432..36c71c3e8 100644
--- a/src/Spectre.Console/Rendering/Borders/Tables/HeavyHeadTableBorder.cs
+++ b/src/Spectre.Console/Rendering/Borders/Tables/HeavyHeadTableBorder.cs
@@ -35,6 +35,10 @@ public override string GetPart(TableBorderPart part)
             TableBorderPart.FooterBottom => "─",
             TableBorderPart.FooterBottomSeparator => "┴",
             TableBorderPart.FooterBottomRight => "┘",
+            TableBorderPart.RowLeft => "├",
+            TableBorderPart.RowCenter => "─",
+            TableBorderPart.RowSeparator => "┼",
+            TableBorderPart.RowRight => "┤",
             _ => throw new InvalidOperationException("Unknown border part."),
         };
     }
diff --git a/src/Spectre.Console/Rendering/Borders/Tables/HeavyTableBorder.cs b/src/Spectre.Console/Rendering/Borders/Tables/HeavyTableBorder.cs
index 783f742fe..68d3675e5 100644
--- a/src/Spectre.Console/Rendering/Borders/Tables/HeavyTableBorder.cs
+++ b/src/Spectre.Console/Rendering/Borders/Tables/HeavyTableBorder.cs
@@ -35,6 +35,10 @@ public override string GetPart(TableBorderPart part)
             TableBorderPart.FooterBottom => "━",
             TableBorderPart.FooterBottomSeparator => "┻",
             TableBorderPart.FooterBottomRight => "┛",
+            TableBorderPart.RowLeft => "┣",
+            TableBorderPart.RowCenter => "━",
+            TableBorderPart.RowSeparator => "╋",
+            TableBorderPart.RowRight => "┫",
             _ => throw new InvalidOperationException("Unknown border part."),
         };
     }
diff --git a/src/Spectre.Console/Rendering/Borders/Tables/HorizontalTableBorder.cs b/src/Spectre.Console/Rendering/Borders/Tables/HorizontalTableBorder.cs
index db3021dbf..740f8e5dc 100644
--- a/src/Spectre.Console/Rendering/Borders/Tables/HorizontalTableBorder.cs
+++ b/src/Spectre.Console/Rendering/Borders/Tables/HorizontalTableBorder.cs
@@ -32,6 +32,10 @@ public override string GetPart(TableBorderPart part)
             TableBorderPart.FooterBottom => "─",
             TableBorderPart.FooterBottomSeparator => "─",
             TableBorderPart.FooterBottomRight => "─",
+            TableBorderPart.RowLeft => "─",
+            TableBorderPart.RowCenter => "─",
+            TableBorderPart.RowSeparator => "─",
+            TableBorderPart.RowRight => "─",
             _ => throw new InvalidOperationException("Unknown border part."),
         };
     }
diff --git a/src/Spectre.Console/Rendering/Borders/Tables/MarkdownTableBorder.cs b/src/Spectre.Console/Rendering/Borders/Tables/MarkdownTableBorder.cs
index f338c3960..fa21f1c88 100644
--- a/src/Spectre.Console/Rendering/Borders/Tables/MarkdownTableBorder.cs
+++ b/src/Spectre.Console/Rendering/Borders/Tables/MarkdownTableBorder.cs
@@ -5,6 +5,9 @@ namespace Spectre.Console.Rendering;
 /// </summary>
 public sealed class MarkdownTableBorder : TableBorder
 {
+    /// <inheritdoc />
+    public override bool SupportsRowSeparator => false;
+
     /// <inheritdoc/>
     public override string GetPart(TableBorderPart part)
     {
@@ -32,6 +35,10 @@ public override string GetPart(TableBorderPart part)
             TableBorderPart.FooterBottom => " ",
             TableBorderPart.FooterBottomSeparator => " ",
             TableBorderPart.FooterBottomRight => " ",
+            TableBorderPart.RowLeft => " ",
+            TableBorderPart.RowCenter => " ",
+            TableBorderPart.RowSeparator => " ",
+            TableBorderPart.RowRight => " ",
             _ => throw new InvalidOperationException("Unknown border part."),
         };
     }
diff --git a/src/Spectre.Console/Rendering/Borders/Tables/MinimalDoubleHeadTableBorder.cs b/src/Spectre.Console/Rendering/Borders/Tables/MinimalDoubleHeadTableBorder.cs
index 5fd281e90..8d58d8f69 100644
--- a/src/Spectre.Console/Rendering/Borders/Tables/MinimalDoubleHeadTableBorder.cs
+++ b/src/Spectre.Console/Rendering/Borders/Tables/MinimalDoubleHeadTableBorder.cs
@@ -32,6 +32,10 @@ public override string GetPart(TableBorderPart part)
             TableBorderPart.FooterBottom => " ",
             TableBorderPart.FooterBottomSeparator => " ",
             TableBorderPart.FooterBottomRight => " ",
+            TableBorderPart.RowLeft => " ",
+            TableBorderPart.RowCenter => "─",
+            TableBorderPart.RowSeparator => "┼",
+            TableBorderPart.RowRight => " ",
             _ => throw new InvalidOperationException("Unknown border part."),
         };
     }
diff --git a/src/Spectre.Console/Rendering/Borders/Tables/MinimalHeavyHeadTableBorder.cs b/src/Spectre.Console/Rendering/Borders/Tables/MinimalHeavyHeadTableBorder.cs
index 7c8cffec5..4d9fd6d0f 100644
--- a/src/Spectre.Console/Rendering/Borders/Tables/MinimalHeavyHeadTableBorder.cs
+++ b/src/Spectre.Console/Rendering/Borders/Tables/MinimalHeavyHeadTableBorder.cs
@@ -35,6 +35,10 @@ public override string GetPart(TableBorderPart part)
             TableBorderPart.FooterBottom => " ",
             TableBorderPart.FooterBottomSeparator => " ",
             TableBorderPart.FooterBottomRight => " ",
+            TableBorderPart.RowLeft => " ",
+            TableBorderPart.RowCenter => "─",
+            TableBorderPart.RowSeparator => "┼",
+            TableBorderPart.RowRight => " ",
             _ => throw new InvalidOperationException("Unknown border part."),
         };
     }
diff --git a/src/Spectre.Console/Rendering/Borders/Tables/MinimalTableBorder.cs b/src/Spectre.Console/Rendering/Borders/Tables/MinimalTableBorder.cs
index 06d47aa32..54d1d1740 100644
--- a/src/Spectre.Console/Rendering/Borders/Tables/MinimalTableBorder.cs
+++ b/src/Spectre.Console/Rendering/Borders/Tables/MinimalTableBorder.cs
@@ -32,6 +32,10 @@ public override string GetPart(TableBorderPart part)
             TableBorderPart.FooterBottom => " ",
             TableBorderPart.FooterBottomSeparator => " ",
             TableBorderPart.FooterBottomRight => " ",
+            TableBorderPart.RowLeft => " ",
+            TableBorderPart.RowCenter => "─",
+            TableBorderPart.RowSeparator => "┼",
+            TableBorderPart.RowRight => " ",
             _ => throw new InvalidOperationException("Unknown border part."),
         };
     }
diff --git a/src/Spectre.Console/Rendering/Borders/Tables/NoTableBorder.cs b/src/Spectre.Console/Rendering/Borders/Tables/NoTableBorder.cs
index c251d9bb9..26ed655e9 100644
--- a/src/Spectre.Console/Rendering/Borders/Tables/NoTableBorder.cs
+++ b/src/Spectre.Console/Rendering/Borders/Tables/NoTableBorder.cs
@@ -8,6 +8,9 @@ public sealed class NoTableBorder : TableBorder
     /// <inheritdoc/>
     public override bool Visible => false;
 
+    /// <inheritdoc />
+    public override bool SupportsRowSeparator => false;
+
     /// <inheritdoc/>
     public override string GetPart(TableBorderPart part)
     {
diff --git a/src/Spectre.Console/Rendering/Borders/Tables/RoundedTableBorder.cs b/src/Spectre.Console/Rendering/Borders/Tables/RoundedTableBorder.cs
index 9d02c68fb..5db78ad6f 100644
--- a/src/Spectre.Console/Rendering/Borders/Tables/RoundedTableBorder.cs
+++ b/src/Spectre.Console/Rendering/Borders/Tables/RoundedTableBorder.cs
@@ -35,6 +35,10 @@ public override string GetPart(TableBorderPart part)
             TableBorderPart.FooterBottom => "─",
             TableBorderPart.FooterBottomSeparator => "┴",
             TableBorderPart.FooterBottomRight => "╯",
+            TableBorderPart.RowLeft => "├",
+            TableBorderPart.RowCenter => "─",
+            TableBorderPart.RowSeparator => "┼",
+            TableBorderPart.RowRight => "┤",
             _ => throw new InvalidOperationException("Unknown border part."),
         };
     }
diff --git a/src/Spectre.Console/Rendering/Borders/Tables/SimpleHeavyTableBorder.cs b/src/Spectre.Console/Rendering/Borders/Tables/SimpleHeavyTableBorder.cs
index d8ae95786..1a703ab70 100644
--- a/src/Spectre.Console/Rendering/Borders/Tables/SimpleHeavyTableBorder.cs
+++ b/src/Spectre.Console/Rendering/Borders/Tables/SimpleHeavyTableBorder.cs
@@ -35,6 +35,10 @@ public override string GetPart(TableBorderPart part)
             TableBorderPart.FooterBottom => " ",
             TableBorderPart.FooterBottomSeparator => " ",
             TableBorderPart.FooterBottomRight => " ",
+            TableBorderPart.RowLeft => "─",
+            TableBorderPart.RowCenter => "─",
+            TableBorderPart.RowSeparator => "─",
+            TableBorderPart.RowRight => "─",
             _ => throw new InvalidOperationException("Unknown border part."),
         };
     }
diff --git a/src/Spectre.Console/Rendering/Borders/Tables/SimpleTableBorder.cs b/src/Spectre.Console/Rendering/Borders/Tables/SimpleTableBorder.cs
index 30fa56348..0bc8531fc 100644
--- a/src/Spectre.Console/Rendering/Borders/Tables/SimpleTableBorder.cs
+++ b/src/Spectre.Console/Rendering/Borders/Tables/SimpleTableBorder.cs
@@ -32,6 +32,10 @@ public override string GetPart(TableBorderPart part)
             TableBorderPart.FooterBottom => " ",
             TableBorderPart.FooterBottomSeparator => " ",
             TableBorderPart.FooterBottomRight => " ",
+            TableBorderPart.RowLeft => "─",
+            TableBorderPart.RowCenter => "─",
+            TableBorderPart.RowSeparator => "─",
+            TableBorderPart.RowRight => "─",
             _ => throw new InvalidOperationException("Unknown border part."),
         };
     }
diff --git a/src/Spectre.Console/Rendering/Borders/Tables/SquareTableBorder.cs b/src/Spectre.Console/Rendering/Borders/Tables/SquareTableBorder.cs
index 4e7e73188..e30ce3d52 100644
--- a/src/Spectre.Console/Rendering/Borders/Tables/SquareTableBorder.cs
+++ b/src/Spectre.Console/Rendering/Borders/Tables/SquareTableBorder.cs
@@ -32,6 +32,10 @@ public override string GetPart(TableBorderPart part)
             TableBorderPart.FooterBottom => "─",
             TableBorderPart.FooterBottomSeparator => "┴",
             TableBorderPart.FooterBottomRight => "┘",
+            TableBorderPart.RowLeft => "├",
+            TableBorderPart.RowCenter => "─",
+            TableBorderPart.RowSeparator => "┼",
+            TableBorderPart.RowRight => "┤",
             _ => throw new InvalidOperationException("Unknown border part."),
         };
     }
diff --git a/src/Spectre.Console/Rendering/TablePart.cs b/src/Spectre.Console/Rendering/TablePart.cs
index 1992252a8..16349678d 100644
--- a/src/Spectre.Console/Rendering/TablePart.cs
+++ b/src/Spectre.Console/Rendering/TablePart.cs
@@ -15,6 +15,11 @@ public enum TablePart
     /// </summary>
     HeaderSeparator,
 
+    /// <summary>
+    /// The separator between the rows.
+    /// </summary>
+    RowSeparator,
+
     /// <summary>
     /// The separator between the footer and the cells.
     /// </summary>
diff --git a/src/Spectre.Console/Spectre.Console.csproj b/src/Spectre.Console/Spectre.Console.csproj
index bf1630c76..fdecc8286 100644
--- a/src/Spectre.Console/Spectre.Console.csproj
+++ b/src/Spectre.Console/Spectre.Console.csproj
@@ -36,9 +36,6 @@
     </PackageReference>
   </ItemGroup>
 
-  <ItemGroup>
-    <Folder Include="Widgets\Json\" />
-  </ItemGroup>
 
   <PropertyGroup>
     <DefineConstants>$(DefineConstants)TRACE;WCWIDTH_VISIBILITY_INTERNAL</DefineConstants>
diff --git a/src/Spectre.Console/TableBorder.cs b/src/Spectre.Console/TableBorder.cs
index a20654430..a3397133a 100644
--- a/src/Spectre.Console/TableBorder.cs
+++ b/src/Spectre.Console/TableBorder.cs
@@ -13,7 +13,12 @@ public abstract partial class TableBorder
     /// <summary>
     /// Gets the safe border for this border or <c>null</c> if none exist.
     /// </summary>
-    public virtual TableBorder? SafeBorder { get; }
+    public virtual TableBorder? SafeBorder { get; } = null;
+
+    /// <summary>
+    /// Gets a value indicating whether the border supports row separators or not.
+    /// </summary>
+    public virtual bool SupportsRowSeparator { get; } = true;
 
     /// <summary>
     /// Gets the string representation of a specified table border part.
@@ -81,6 +86,11 @@ public virtual string GetColumnRow(TablePart part, IReadOnlyList<int> widths, IR
                 (GetPart(TableBorderPart.HeaderBottomLeft), GetPart(TableBorderPart.HeaderBottom),
                 GetPart(TableBorderPart.HeaderBottomSeparator), GetPart(TableBorderPart.HeaderBottomRight)),
 
+            // Separator between header and cells
+            TablePart.RowSeparator =>
+                (GetPart(TableBorderPart.RowLeft), GetPart(TableBorderPart.RowCenter),
+                    GetPart(TableBorderPart.RowSeparator), GetPart(TableBorderPart.RowRight)),
+
             // Separator between footer and cells
             TablePart.FooterSeparator =>
                 (GetPart(TableBorderPart.FooterTopLeft), GetPart(TableBorderPart.FooterTop),
diff --git a/src/Spectre.Console/Widgets/Panel.cs b/src/Spectre.Console/Widgets/Panel.cs
index 05d41d459..7a34d9592 100644
--- a/src/Spectre.Console/Widgets/Panel.cs
+++ b/src/Spectre.Console/Widgets/Panel.cs
@@ -132,6 +132,15 @@ protected override IEnumerable<Segment> Render(RenderOptions options, int maxWid
         {
             AddTopBorder(result, options, border, borderStyle, panelWidth);
         }
+        else
+        {
+            // Not showing border, but we have a header text?
+            // Use a invisible border to draw the top border
+            if (Header?.Text != null)
+            {
+                AddTopBorder(result, options, BoxBorder.None, borderStyle, panelWidth);
+            }
+        }
 
         // Split the child segments into lines.
         var childSegments = ((IRenderable)child).Render(options with { Height = height }, innerWidth);
diff --git a/src/Spectre.Console/Widgets/Table/Table.cs b/src/Spectre.Console/Widgets/Table/Table.cs
index c0d83e186..345e83d7a 100644
--- a/src/Spectre.Console/Widgets/Table/Table.cs
+++ b/src/Spectre.Console/Widgets/Table/Table.cs
@@ -31,6 +31,11 @@ public sealed class Table : Renderable, IHasTableBorder, IExpandable, IAlignable
     /// </summary>
     public bool ShowHeaders { get; set; } = true;
 
+    /// <summary>
+    /// Gets or sets a value indicating whether or not row separators should be shown.
+    /// </summary>
+    public bool ShowRowSeparators { get; set; }
+
     /// <summary>
     /// Gets or sets a value indicating whether or not table footers should be shown.
     /// </summary>
diff --git a/src/Spectre.Console/Widgets/Table/TableRenderer.cs b/src/Spectre.Console/Widgets/Table/TableRenderer.cs
index d27326e78..23bff61cd 100644
--- a/src/Spectre.Console/Widgets/Table/TableRenderer.cs
+++ b/src/Spectre.Console/Widgets/Table/TableRenderer.cs
@@ -36,7 +36,9 @@ public static List<Segment> Render(TableRendererContext context, List<int> colum
             // Show top of header?
             if (isFirstRow && context.ShowBorder)
             {
-                var separator = Aligner.Align(context.Border.GetColumnRow(TablePart.Top, columnWidths, context.Columns), context.Alignment, context.MaxWidth);
+                var separator = Aligner.Align(
+                    context.Border.GetColumnRow(TablePart.Top, columnWidths, context.Columns),
+                    context.Alignment, context.MaxWidth);
                 result.Add(new Segment(separator, context.BorderStyle));
                 result.Add(Segment.LineBreak);
             }
@@ -66,7 +68,9 @@ public static List<Segment> Render(TableRendererContext context, List<int> colum
                     if (isFirstCell && context.ShowBorder)
                     {
                         // Show left column edge
-                        var part = isFirstRow && context.ShowHeaders ? TableBorderPart.HeaderLeft : TableBorderPart.CellLeft;
+                        var part = isFirstRow && context.ShowHeaders
+                            ? TableBorderPart.HeaderLeft
+                            : TableBorderPart.CellLeft;
                         rowResult.Add(new Segment(context.Border.GetPart(part), context.BorderStyle));
                     }
 
@@ -91,7 +95,8 @@ public static List<Segment> Render(TableRendererContext context, List<int> colum
                     }
 
                     // Pad column on the right side
-                    if (context.ShowBorder || (context.HideBorder && !isLastCell) || (context.HideBorder && isLastCell && context.IsGrid && context.PadRightCell))
+                    if (context.ShowBorder || (context.HideBorder && !isLastCell) ||
+                        (context.HideBorder && isLastCell && context.IsGrid && context.PadRightCell))
                     {
                         var rightPadding = context.Columns[cellIndex].Padding.GetRightSafe();
                         if (rightPadding > 0)
@@ -103,13 +108,17 @@ public static List<Segment> Render(TableRendererContext context, List<int> colum
                     if (isLastCell && context.ShowBorder)
                     {
                         // Add right column edge
-                        var part = isFirstRow && context.ShowHeaders ? TableBorderPart.HeaderRight : TableBorderPart.CellRight;
+                        var part = isFirstRow && context.ShowHeaders
+                            ? TableBorderPart.HeaderRight
+                            : TableBorderPart.CellRight;
                         rowResult.Add(new Segment(context.Border.GetPart(part), context.BorderStyle));
                     }
                     else if (context.ShowBorder)
                     {
                         // Add column separator
-                        var part = isFirstRow && context.ShowHeaders ? TableBorderPart.HeaderSeparator : TableBorderPart.CellSeparator;
+                        var part = isFirstRow && context.ShowHeaders
+                            ? TableBorderPart.HeaderSeparator
+                            : TableBorderPart.CellSeparator;
                         rowResult.Add(new Segment(context.Border.GetPart(part), context.BorderStyle));
                     }
                 }
@@ -133,15 +142,40 @@ public static List<Segment> Render(TableRendererContext context, List<int> colum
             // Show header separator?
             if (isFirstRow && context.ShowBorder && context.ShowHeaders && context.HasRows)
             {
-                var separator = Aligner.Align(context.Border.GetColumnRow(TablePart.HeaderSeparator, columnWidths, context.Columns), context.Alignment, context.MaxWidth);
+                var separator =
+                    Aligner.Align(
+                        context.Border.GetColumnRow(TablePart.HeaderSeparator, columnWidths, context.Columns),
+                        context.Alignment, context.MaxWidth);
                 result.Add(new Segment(separator, context.BorderStyle));
                 result.Add(Segment.LineBreak);
             }
 
+            // Show row separator?
+            if (context.Border.SupportsRowSeparator && context.ShowRowSeparators
+                                                    && !isFirstRow && !isLastRow)
+            {
+                var hasVisibleFootes = context is { ShowFooters: true, HasFooters: true };
+                var isNextLastLine = index == context.Rows.Count - 2;
+
+                var isRenderingFooter = hasVisibleFootes && isNextLastLine;
+                if (!isRenderingFooter)
+                {
+                    var separator =
+                        Aligner.Align(
+                            context.Border.GetColumnRow(TablePart.RowSeparator, columnWidths, context.Columns),
+                            context.Alignment, context.MaxWidth);
+                    result.Add(new Segment(separator, context.BorderStyle));
+                    result.Add(Segment.LineBreak);
+                }
+            }
+
             // Show bottom of footer?
             if (isLastRow && context.ShowBorder)
             {
-                var separator = Aligner.Align(context.Border.GetColumnRow(TablePart.Bottom, columnWidths, context.Columns), context.Alignment, context.MaxWidth);
+                var separator =
+                    Aligner.Align(
+                        context.Border.GetColumnRow(TablePart.Bottom, columnWidths, context.Columns),
+                        context.Alignment, context.MaxWidth);
                 result.Add(new Segment(separator, context.BorderStyle));
                 result.Add(Segment.LineBreak);
             }
@@ -151,7 +185,8 @@ public static List<Segment> Render(TableRendererContext context, List<int> colum
         return result;
     }
 
-    private static IEnumerable<Segment> RenderAnnotation(TableRendererContext context, TableTitle? header, Style defaultStyle)
+    private static IEnumerable<Segment> RenderAnnotation(TableRendererContext context, TableTitle? header,
+        Style defaultStyle)
     {
         if (header == null)
         {
diff --git a/src/Spectre.Console/Widgets/Table/TableRendererContext.cs b/src/Spectre.Console/Widgets/Table/TableRendererContext.cs
index b54980248..ad9b709a2 100644
--- a/src/Spectre.Console/Widgets/Table/TableRendererContext.cs
+++ b/src/Spectre.Console/Widgets/Table/TableRendererContext.cs
@@ -10,6 +10,7 @@ internal sealed class TableRendererContext : TableAccessor
     public TableBorder Border { get; }
     public Style BorderStyle { get; }
     public bool ShowBorder { get; }
+    public bool ShowRowSeparators { get; }
     public bool HasRows { get; }
     public bool HasFooters { get; }
 
@@ -47,6 +48,7 @@ public TableRendererContext(Table table, RenderOptions options, IEnumerable<Tabl
         HasFooters = Rows.Any(column => column.IsFooter);
         Border = table.Border.GetSafeBorder(!options.Unicode && table.UseSafeBorder);
         BorderStyle = table.BorderStyle ?? Style.Plain;
+        ShowRowSeparators = table.ShowRowSeparators;
 
         TableWidth = tableWidth;
         MaxWidth = maxWidth;


Test patch:
diff --git a/test/Spectre.Console.Tests/Expectations/Rendering/Borders/Box/NoBorder.Output.verified.txt b/test/Spectre.Console.Tests/Expectations/Rendering/Borders/Box/NoBorder.Output.verified.txt
index 0d3e931ff..a254250d8 100644
--- a/test/Spectre.Console.Tests/Expectations/Rendering/Borders/Box/NoBorder.Output.verified.txt
+++ b/test/Spectre.Console.Tests/Expectations/Rendering/Borders/Box/NoBorder.Output.verified.txt
@@ -1,1 +1,1 @@
- Hello World 
+﻿ Hello World 
diff --git a/test/Spectre.Console.Tests/Expectations/Rendering/Borders/Box/NoBorder_With_Header.Output.verified.txt b/test/Spectre.Console.Tests/Expectations/Rendering/Borders/Box/NoBorder_With_Header.Output.verified.txt
new file mode 100644
index 000000000..2ad34aef5
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Rendering/Borders/Box/NoBorder_With_Header.Output.verified.txt
@@ -0,0 +1,2 @@
+﻿  Greeting   
+ Hello World 
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Table/Render_Row_Separators.Output.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Table/Render_Row_Separators.Output.verified.txt
new file mode 100644
index 000000000..02d8f0d7d
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Table/Render_Row_Separators.Output.verified.txt
@@ -0,0 +1,7 @@
+﻿┌────────┬────────┬───────┐
+│ Foo    │ Bar    │ Baz   │
+├────────┼────────┼───────┤
+│ Qux    │ Corgi  │ Waldo │
+├────────┼────────┼───────┤
+│ Grault │ Garply │ Fred  │
+└────────┴────────┴───────┘
diff --git a/test/Spectre.Console.Tests/Unit/Rendering/Borders/BoxBorderTests.cs b/test/Spectre.Console.Tests/Unit/Rendering/Borders/BoxBorderTests.cs
index f16fe9775..d9643cbf8 100644
--- a/test/Spectre.Console.Tests/Unit/Rendering/Borders/BoxBorderTests.cs
+++ b/test/Spectre.Console.Tests/Unit/Rendering/Borders/BoxBorderTests.cs
@@ -23,6 +23,22 @@ public void Should_Return_Safe_Border()
         [Fact]
         [Expectation("NoBorder")]
         public Task Should_Render_As_Expected()
+        {
+            // Given
+            var console = new TestConsole();
+            var panel = Fixture.GetPanel().NoBorder();
+            panel.Header = null;
+
+            // When
+            console.Write(panel);
+
+            // Then
+            return Verifier.Verify(console.Output);
+        }
+
+        [Fact]
+        [Expectation("NoBorder_With_Header")]
+        public Task Should_Render_NoBorder_With_Header_As_Expected()
         {
             // Given
             var console = new TestConsole();
diff --git a/test/Spectre.Console.Tests/Unit/Widgets/Table/TableTests.cs b/test/Spectre.Console.Tests/Unit/Widgets/Table/TableTests.cs
index a8fb9082f..8a1ce1d36 100644
--- a/test/Spectre.Console.Tests/Unit/Widgets/Table/TableTests.cs
+++ b/test/Spectre.Console.Tests/Unit/Widgets/Table/TableTests.cs
@@ -142,6 +142,25 @@ public Task Should_Render_Table_Correctly()
         return Verifier.Verify(console.Output);
     }
 
+    [Fact]
+    [Expectation("Render_Row_Separators")]
+    public Task Should_Render_Table_With_Row_Separators_Correctly()
+    {
+        // Given
+        var console = new TestConsole();
+        var table = new Table();
+        table.ShowRowSeparators();
+        table.AddColumns("Foo", "Bar", "Baz");
+        table.AddRow("Qux", "Corgi", "Waldo");
+        table.AddRow("Grault", "Garply", "Fred");
+
+        // When
+        console.Write(table);
+
+        // Then
+        return Verifier.Verify(console.Output);
+    }
+
     [Fact]
     [Expectation("Render_EA_Character")]
     public Task Should_Render_Table_With_EA_Character_Correctly()