Repository: jedib0t/go-pretty

Issue:
Custom top and bottom table style
First, thanks for the great library. It is excellent.

It would be nice to be able to customize the first and last rows of the table different from the middle ones.

From what I've seen, `MiddleHorizontal` (`t.Style().Box.MiddleHorizontal`) controls the style of all horizontal lines. However, it would be nice to have independent `Top` and `Bottom` options.

These options could generate the following tables, which I didn't get in the current settings:

```
   #   FIRST NAME   LAST NAME   SALARY                                  <- no top line
----- ------------ ----------- -------- -----------------------------
   1   Arya         Stark         3000                               
  20   Jon          Snow          2000   You know nothing, Jon Snow! 
 300   Tyrion       Lannister     5000                               
----- ------------ ----------- -------- -----------------------------
                    TOTAL        10000                               
----- ------------ ----------- -------- ----------------------------- <- with bottom line
```

```
=====+============+===========+========+============================= <- diferrent styles
   # | FIRST NAME | LAST NAME | SALARY |                             
-----+------------+-----------+--------+----------------------------- <- diferrent styles
   1 | Arya       | Stark     |   3000 |                             
  20 | Jon        | Snow      |   2000 | You know nothing, Jon Snow! 
 300 | Tyrion     | Lannister |   5000 |                             
-----+------------+-----------+--------+-----------------------------
     |            | TOTAL     |  10000 |                             
=====+============+===========+========+=============================
```

```
^^^^^+^^^^^^^^^^^^+^^^^^^^^^^^+^^^^^^^^+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ <- diferrent styles
   # | FIRST NAME | LAST NAME | SALARY |                             
-----+------------+-----------+--------+----------------------------- <- diferrent styles
   1 | Arya       | Stark     |   3000 |                             
  20 | Jon        | Snow      |   2000 | You know nothing, Jon Snow! 
 300 | Tyrion     | Lannister |   5000 |                             
-----+------------+-----------+--------+-----------------------------
     |            | TOTAL     |  10000 |                             
~~~~~+~~~~~~~~~~~~+~~~~~~~~~~~+~~~~~~~~+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ <- diferrent styles
```

With this feature, custom styles would become more generic.

Hints from issue comments:
@jedib0t: This is possible to do. Will try to get this implemented.@jedib0t: The implementation is complex simply because this introduces a LOT of edge cases (literally). Just thought I'd let you know to not expect this soon. Will update this thread when I make some progress.@jedib0t: Hey @manoelmozzer ... I honestly tried to make this customization work, but the more I worked on it, the more it felt like a lot of code quality/simplicity/maintainability had to be sacrificed to make this work.

I have code that can stylize Title/Header/Rows/Footers all separately in their own individual styles. But this change required a bunch of backward-incompatible changes and a major version bump along with making the code really hard to read/reason/maintain.

I'll leave this issue open, and if a lot more folks ask for this feature, I'll see what I can do. But for now, I'm gonna stop working on this. Sorry! 😞 @manoelmozzer: No problem, thanks for your try. Anyway, your library is great. xD@stingshen: [RST grid table](https://tables-with-sphinx.readthedocs.io/en/latest/grid-table.html) can be rendered if this feature is implemented.@jedib0t: If you are still interested, I finally got this done with #386. Available for use in tag [v6.7.4](https://github.com/jedib0t/go-pretty/releases/tag/v6.7.4).@manoelmozzer: Hey, what a great surprise! I still use this module to create tables, it will be a welcome addition.

Thank you!

Solution patch:
diff --git a/table/EXAMPLES.md b/table/EXAMPLES.md
new file mode 100644
index 0000000..ffb8483
--- /dev/null
+++ b/table/EXAMPLES.md
@@ -0,0 +1,579 @@
+# Examples
+
+All the examples below are going to start with the following block, although
+nothing except a single Row is mandatory for the `Render()` function to render
+something:
+```golang
+package main
+
+import (
+    "os"
+
+    "github.com/jedib0t/go-pretty/v6/table"
+)
+
+func main() {
+    t := table.NewWriter()
+    t.SetOutputMirror(os.Stdout)
+    t.AppendHeader(table.Row{"#", "First Name", "Last Name", "Salary"})
+    t.AppendRows([]table.Row{
+        {1, "Arya", "Stark", 3000},
+        {20, "Jon", "Snow", 2000, "You know nothing, Jon Snow!"},
+    })
+    t.AppendSeparator()
+    t.AppendRow([]interface{}{300, "Tyrion", "Lannister", 5000})
+    t.AppendFooter(table.Row{"", "", "Total", 10000})
+    t.Render()
+}
+```
+Running the above will result in:
+```
++-----+------------+-----------+--------+-----------------------------+
+|   # | FIRST NAME | LAST NAME | SALARY |                             |
++-----+------------+-----------+--------+-----------------------------+
+|   1 | Arya       | Stark     |   3000 |                             |
+|  20 | Jon        | Snow      |   2000 | You know nothing, Jon Snow! |
++-----+------------+-----------+--------+-----------------------------+
+| 300 | Tyrion     | Lannister |   5000 |                             |
++-----+------------+-----------+--------+-----------------------------+
+|     |            | TOTAL     |  10000 |                             |
++-----+------------+-----------+--------+-----------------------------+
+```
+
+---
+
+<details>
+<summary><strong>Ready-to-use Styles</strong></summary>
+
+Table comes with a bunch of ready-to-use Styles that make the table look really
+good. Set or Change the style using:
+```golang
+    t.SetStyle(table.StyleLight)
+    t.Render()
+```
+to get:
+```
+┌─────┬────────────┬───────────┬────────┬─────────────────────────────┐
+│   # │ FIRST NAME │ LAST NAME │ SALARY │                             │
+├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
+│   1 │ Arya       │ Stark     │   3000 │                             │
+│  20 │ Jon        │ Snow      │   2000 │ You know nothing, Jon Snow! │
+├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
+│ 300 │ Tyrion     │ Lannister │   5000 │                             │
+├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
+│     │            │ TOTAL     │  10000 │                             │
+└─────┴────────────┴───────────┴────────┴─────────────────────────────┘
+```
+
+Or if you want to use a full-color mode, and don't care for boxes, use:
+```golang
+    t.SetStyle(table.StyleColoredBright)
+    t.Render()
+```
+to get:
+
+<img src="images/table-StyleColoredBright.png" width="640px" alt="Colored Table"/>
+
+</details>
+
+---
+
+<details>
+<summary><strong>Roll your own Style</strong></summary>
+
+You can also roll your own style:
+```golang
+    t.SetStyle(table.Style{
+        Name: "myNewStyle",
+        Box: table.BoxStyle{
+            BottomLeft:       "\\",
+            BottomRight:      "/",
+            BottomSeparator:  "v",
+            Left:             "[",
+            LeftSeparator:    "{",
+            MiddleHorizontal: "-",
+            MiddleSeparator:  "+",
+            MiddleVertical:   "|",
+            PaddingLeft:      "<",
+            PaddingRight:     ">",
+            Right:            "]",
+            RightSeparator:   "}",
+            TopLeft:          "(",
+            TopRight:         ")",
+            TopSeparator:     "^",
+            UnfinishedRow:    " ~~~",
+        },
+        Color: table.ColorOptions{
+            IndexColumn:     text.Colors{text.BgCyan, text.FgBlack},
+            Footer:          text.Colors{text.BgCyan, text.FgBlack},
+            Header:          text.Colors{text.BgHiCyan, text.FgBlack},
+            Row:             text.Colors{text.BgHiWhite, text.FgBlack},
+            RowAlternate:    text.Colors{text.BgWhite, text.FgBlack},
+        },
+        Format: table.FormatOptions{
+            Footer: text.FormatUpper,
+            Header: text.FormatUpper,
+            Row:    text.FormatDefault,
+        },
+        Options: table.Options{
+            DrawBorder:      true,
+            SeparateColumns: true,
+            SeparateFooter:  true,
+            SeparateHeader:  true,
+            SeparateRows:    false,
+        },
+    })
+```
+
+Or you can use one of the ready-to-use Styles, and just make a few tweaks:
+```golang
+    t.SetStyle(table.StyleLight)
+    t.Style().Color.Header = text.Colors{text.BgHiCyan, text.FgBlack}
+    t.Style().Color.IndexColumn = text.Colors{text.BgHiCyan, text.FgBlack}
+    t.Style().Format.Footer = text.FormatLower
+    t.Style().Options.DrawBorder = false
+```
+
+</details>
+
+---
+
+<details>
+<summary><strong>Customize Horizontal Separators</strong></summary>
+
+For more granular control over horizontal lines in your table, you can use `BoxStyleHorizontal` to customize different separator types independently. This allows you to have different horizontal line styles for titles, headers, rows, and footers.
+
+The `BoxStyleHorizontal` struct provides 10 customizable separator types:
+- `TitleTop` / `TitleBottom` - Lines above/below the title
+- `HeaderTop` / `HeaderMiddle` / `HeaderBottom` - Lines in the header section
+- `RowTop` / `RowMiddle` / `RowBottom` - Lines in the data rows section
+- `FooterTop` / `FooterMiddle` / `FooterBottom` - Lines in the footer section
+
+You can customize each separator type using:
+
+```golang
+    tw := table.NewWriter()
+    tw.AppendHeader(table.Row{"#", "First Name", "Last Name", "Salary"})
+    tw.AppendRows([]table.Row{
+        {1, "Arya", "Stark", 3000},
+        {20, "Jon", "Snow", 2000, "You know nothing, Jon Snow!"},
+        {300, "Tyrion", "Lannister", 5000},
+    })
+    tw.AppendFooter(table.Row{"", "", "Total", 10000})
+    tw.SetStyle(table.StyleDefault)
+    tw.Style().Box.Horizontal = &table.BoxStyleHorizontal{
+        HeaderTop:    "=", // Thicker line above header
+        HeaderMiddle: "-",
+        HeaderBottom: "~", // Thicker line below header
+        RowTop:       "-",
+        RowMiddle:    "- ",
+        RowBottom:    "-",
+        FooterTop:    "~", // Thicker line above footer
+        FooterMiddle: "-",
+        FooterBottom: "=", // Thicker line below footer
+    }
+    tw.Style().Options.SeparateRows = true
+    fmt.Println(tw.Render())
+```
+to get something like:
+```
++=====+============+===========+========+=============================+
+|   # | FIRST NAME | LAST NAME | SALARY |                             |
++~~~~~+~~~~~~~~~~~~+~~~~~~~~~~~+~~~~~~~~+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
+|   1 | Arya       | Stark     |   3000 |                             |
++- - -+- - - - - - +- - - - - -+- - - - +- - - - - - - - - - - - - - -+
+|  20 | Jon        | Snow      |   2000 | You know nothing, Jon Snow! |
++- - -+- - - - - - +- - - - - -+- - - - +- - - - - - - - - - - - - - -+
+| 300 | Tyrion     | Lannister |   5000 |                             |
++~~~~~+~~~~~~~~~~~~+~~~~~~~~~~~+~~~~~~~~+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
+|     |            | TOTAL     |  10000 |                             |
++=====+============+===========+========+=============================+
+```
+
+When `BoxStyle.Horizontal` is set to a non-nil value, it overrides the `MiddleHorizontal` string for all horizontal separators. If `Horizontal` is nil, the table will automatically use `MiddleHorizontal` for all separator types.
+
+</details>
+
+---
+
+<details>
+<summary><strong>Auto-Merge</strong></summary>
+
+You can auto-merge cells horizontally and vertically, but you have request for
+it specifically for each row/column using `RowConfig` or `ColumnConfig`.
+
+```golang
+    rowConfigAutoMerge := table.RowConfig{AutoMerge: true}
+
+    t := table.NewWriter()
+    t.AppendHeader(table.Row{"Node IP", "Pods", "Namespace", "Container", "RCE", "RCE"}, rowConfigAutoMerge)
+    t.AppendHeader(table.Row{"Node IP", "Pods", "Namespace", "Container", "EXE", "RUN"})
+    t.AppendRow(table.Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 1", "Y", "Y"}, rowConfigAutoMerge)
+    t.AppendRow(table.Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 2", "Y", "N"}, rowConfigAutoMerge)
+    t.AppendRow(table.Row{"1.1.1.1", "Pod 1A", "NS 1B", "C 3", "N", "N"}, rowConfigAutoMerge)
+    t.AppendRow(table.Row{"1.1.1.1", "Pod 1B", "NS 2", "C 4", "N", "N"}, rowConfigAutoMerge)
+    t.AppendRow(table.Row{"1.1.1.1", "Pod 1B", "NS 2", "C 5", "Y", "N"}, rowConfigAutoMerge)
+    t.AppendRow(table.Row{"2.2.2.2", "Pod 2", "NS 3", "C 6", "Y", "Y"}, rowConfigAutoMerge)
+    t.AppendRow(table.Row{"2.2.2.2", "Pod 2", "NS 3", "C 7", "Y", "Y"}, rowConfigAutoMerge)
+    t.AppendFooter(table.Row{"", "", "", 7, 5, 3})
+    t.SetAutoIndex(true)
+    t.SetColumnConfigs([]table.ColumnConfig{
+        {Number: 1, AutoMerge: true},
+        {Number: 2, AutoMerge: true},
+        {Number: 3, AutoMerge: true},
+        {Number: 4, AutoMerge: true},
+        {Number: 5, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter},
+        {Number: 6, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter},
+    })
+    t.SetStyle(table.StyleLight)
+    t.Style().Options.SeparateRows = true
+    fmt.Println(t.Render())
+```
+to get:
+```
+┌───┬─────────┬────────┬───────────┬───────────┬───────────┐
+│   │ NODE IP │ PODS   │ NAMESPACE │ CONTAINER │    RCE    │
+│   │         │        │           │           ├─────┬─────┤
+│   │         │        │           │           │ EXE │ RUN │
+├───┼─────────┼────────┼───────────┼───────────┼─────┴─────┤
+│ 1 │ 1.1.1.1 │ Pod 1A │ NS 1A     │ C 1       │     Y     │
+├───┤         │        │           ├───────────┼─────┬─────┤
+│ 2 │         │        │           │ C 2       │  Y  │  N  │
+├───┤         │        ├───────────┼───────────┼─────┴─────┤
+│ 3 │         │        │ NS 1B     │ C 3       │     N     │
+├───┤         ├────────┼───────────┼───────────┼───────────┤
+│ 4 │         │ Pod 1B │ NS 2      │ C 4       │     N     │
+├───┤         │        │           ├───────────┼─────┬─────┤
+│ 5 │         │        │           │ C 5       │  Y  │  N  │
+├───┼─────────┼────────┼───────────┼───────────┼─────┴─────┤
+│ 6 │ 2.2.2.2 │ Pod 2  │ NS 3      │ C 6       │     Y     │
+├───┤         │        │           ├───────────┼───────────┤
+│ 7 │         │        │           │ C 7       │     Y     │
+├───┼─────────┼────────┼───────────┼───────────┼─────┬─────┤
+│   │         │        │           │ 7         │  5  │  3  │
+└───┴─────────┴────────┴───────────┴───────────┴─────┴─────┘
+```
+
+</details>
+
+---
+
+<details>
+<summary><strong>Paging</strong></summary>
+
+You can limit the number of lines rendered in a single "Page". This logic
+can handle rows with multiple lines too. The recommended way is to use the
+`Pager()` interface:
+
+```golang
+    pager := t.Pager(PageSize(1))
+    pager.Render()  // Render first page
+    pager.Next()    // Move to next page and render
+    pager.Prev()    // Move to previous page and render
+    pager.GoTo(3)   // Jump to page 3
+    pager.Location() // Get current page number
+```
+
+Or use the deprecated `SetPageSize()` method for simple cases:
+```golang
+    t.SetPageSize(1)
+    t.Render()
+```
+to get:
+```
++-----+------------+-----------+--------+-----------------------------+
+|   # | FIRST NAME | LAST NAME | SALARY |                             |
++-----+------------+-----------+--------+-----------------------------+
+|   1 | Arya       | Stark     |   3000 |                             |
++-----+------------+-----------+--------+-----------------------------+
+|     |            | TOTAL     |  10000 |                             |
++-----+------------+-----------+--------+-----------------------------+
+
++-----+------------+-----------+--------+-----------------------------+
+|   # | FIRST NAME | LAST NAME | SALARY |                             |
++-----+------------+-----------+--------+-----------------------------+
+|  20 | Jon        | Snow      |   2000 | You know nothing, Jon Snow! |
++-----+------------+-----------+--------+-----------------------------+
+|     |            | TOTAL     |  10000 |                             |
++-----+------------+-----------+--------+-----------------------------+
+
++-----+------------+-----------+--------+-----------------------------+
+|   # | FIRST NAME | LAST NAME | SALARY |                             |
++-----+------------+-----------+--------+-----------------------------+
+| 300 | Tyrion     | Lannister |   5000 |                             |
++-----+------------+-----------+--------+-----------------------------+
+|     |            | TOTAL     |  10000 |                             |
++-----+------------+-----------+--------+-----------------------------+
+```
+
+</details>
+
+---
+
+<details>
+<summary><strong>Sorting</strong></summary>
+
+Sorting can be done on one or more columns. The following code will make the
+rows be sorted first by "First Name" and then by "Last Name" (in case of similar
+"First Name" entries).
+```golang
+    t.SortBy([]table.SortBy{
+	    {Name: "First Name", Mode: table.Asc},
+	    {Name: "Last Name", Mode: table.Asc},
+    })
+```
+
+The `Mode` field in `SortBy` supports various sorting modes:
+- `Asc` / `Dsc` - Alphabetical ascending/descending
+- `AscNumeric` / `DscNumeric` - Numerical ascending/descending
+- `AscAlphaNumeric` / `DscAlphaNumeric` - Alphabetical first, then numerical
+- `AscNumericAlpha` / `DscNumericAlpha` - Numerical first, then alphabetical
+
+You can also make sorting case-insensitive by setting `IgnoreCase: true`:
+```golang
+    t.SortBy([]table.SortBy{
+	    {Name: "First Name", Mode: table.Asc, IgnoreCase: true},
+    })
+```
+
+</details>
+
+---
+
+<details>
+<summary><strong>Sorting Customization</strong></summary>
+
+For advanced sorting requirements, you can provide a custom comparison function
+using `CustomLess`. This function overrides the `Mode` and `IgnoreCase` settings
+and gives you full control over the sorting logic.
+
+The `CustomLess` function receives two string values (the cell contents converted
+to strings) and must return:
+- `-1` when the first value should come before the second
+- `0` when the values are considered equal (sorting continues to the next column)
+- `1` when the first value should come after the second
+
+Example: Custom numeric sorting that handles string numbers correctly.
+```golang
+    t.SortBy([]table.SortBy{
+	    {
+		    Number: 1,
+		    CustomLess: func(iStr string, jStr string) int {
+			    iNum, iErr := strconv.Atoi(iStr)
+			    jNum, jErr := strconv.Atoi(jStr)
+			    if iErr != nil || jErr != nil {
+				    // Fallback to string comparison if not numeric
+				    if iStr < jStr {
+					    return -1
+				    }
+				    if iStr > jStr {
+					    return 1
+				    }
+				    return 0
+			    }
+			    if iNum < jNum {
+				    return -1
+			    }
+			    if iNum > jNum {
+				    return 1
+			    }
+			    return 0
+		    },
+	    },
+    })
+```
+
+Example: Combining custom sorting with default sorting modes.
+```golang
+    t.SortBy([]table.SortBy{
+	    {
+		    Number: 1,
+		    CustomLess: func(iStr string, jStr string) int {
+			    // Custom logic: "same" values come first
+			    if iStr == "same" && jStr != "same" {
+				    return -1
+			    }
+			    if iStr != "same" && jStr == "same" {
+				    return 1
+			    }
+			    return 0 // Equal, continue to next column
+		    },
+	    },
+	    {Number: 2, Mode: table.Asc},        // Default alphabetical sort
+	    {Number: 3, Mode: table.AscNumeric}, // Default numeric sort
+    })
+```
+</details>
+
+---
+
+<details>
+<summary><strong>Wrapping (or) Row/Column Width restrictions</strong></summary>
+
+You can restrict the maximum (text) width for a Row:
+```golang
+    t.SetAllowedRowLength(50)
+    t.Render()
+```
+to get:
+```
++-----+------------+-----------+--------+------- ~
+|   # | FIRST NAME | LAST NAME | SALARY |        ~
++-----+------------+-----------+--------+------- ~
+|   1 | Arya       | Stark     |   3000 |        ~
+|  20 | Jon        | Snow      |   2000 | You kn ~
++-----+------------+-----------+--------+------- ~
+| 300 | Tyrion     | Lannister |   5000 |        ~
++-----+------------+-----------+--------+------- ~
+|     |            | TOTAL     |  10000 |        ~
++-----+------------+-----------+--------+------- ~
+```
+
+</details>
+
+---
+
+<details>
+<summary><strong>Column Control - Alignment, Colors, Width and more</strong></summary>
+
+You can control a lot of things about individual cells/columns which overrides
+global properties/styles using the `SetColumnConfig()` interface:
+- Alignment (horizontal & vertical)
+- Colorization
+- Transform individual cells based on the content
+- Visibility
+- Width (minimum & maximum)
+
+```golang
+    nameTransformer := text.Transformer(func(val interface{}) string {
+        return text.Bold.Sprint(val)
+    })
+
+    t.SetColumnConfigs([]ColumnConfig{
+        {
+            Name:              "First Name",
+            Align:             text.AlignLeft,
+            AlignFooter:       text.AlignLeft,
+            AlignHeader:       text.AlignLeft,
+            Colors:            text.Colors{text.BgBlack, text.FgRed},
+            ColorsHeader:      text.Colors{text.BgRed, text.FgBlack, text.Bold},
+            ColorsFooter:      text.Colors{text.BgRed, text.FgBlack},
+            Hidden:            false,
+            Transformer:       nameTransformer,
+            TransformerFooter: nameTransformer,
+            TransformerHeader: nameTransformer,
+            VAlign:            text.VAlignMiddle,
+            VAlignFooter:      text.VAlignTop,
+            VAlignHeader:      text.VAlignBottom,
+            WidthMin:          6,
+            WidthMax:          64,
+        }
+    })
+```
+
+</details>
+
+---
+
+<details>
+<summary><strong>CSV</strong></summary>
+
+```golang
+    t.RenderCSV()
+```
+to get:
+```
+,First Name,Last Name,Salary,
+1,Arya,Stark,3000,
+20,Jon,Snow,2000,"You know nothing\, Jon Snow!"
+300,Tyrion,Lannister,5000,
+,,Total,10000,
+```
+
+</details>
+
+---
+
+<details>
+<summary><strong>HTML Table</strong></summary>
+
+```golang
+    t.Style().HTML = table.HTMLOptions{
+        CSSClass:             "game-of-thrones",
+        EmptyColumn:          "&nbsp;",
+        EscapeText:           true,
+        Newline:              "<br/>",
+        ConvertColorsToSpans: true, // Convert ANSI escape sequences to HTML <span> tags with CSS classes
+    }
+    t.RenderHTML()
+```
+to get:
+```html
+<table class="game-of-thrones">
+  <thead>
+  <tr>
+    <th align="right">#</th>
+    <th>First Name</th>
+    <th>Last Name</th>
+    <th align="right">Salary</th>
+    <th>&nbsp;</th>
+  </tr>
+  </thead>
+  <tbody>
+  <tr>
+    <td align="right">1</td>
+    <td>Arya</td>
+    <td>Stark</td>
+    <td align="right">3000</td>
+    <td>&nbsp;</td>
+  </tr>
+  <tr>
+    <td align="right">20</td>
+    <td>Jon</td>
+    <td>Snow</td>
+    <td align="right">2000</td>
+    <td>You know nothing, Jon Snow!</td>
+  </tr>
+  <tr>
+    <td align="right">300</td>
+    <td>Tyrion</td>
+    <td>Lannister</td>
+    <td align="right">5000</td>
+    <td>&nbsp;</td>
+  </tr>
+  </tbody>
+  <tfoot>
+  <tr>
+    <td align="right">&nbsp;</td>
+    <td>&nbsp;</td>
+    <td>Total</td>
+    <td align="right">10000</td>
+    <td>&nbsp;</td>
+  </tr>
+  </tfoot>
+</table>
+```
+
+</details>
+
+---
+
+<details>
+<summary><strong>Markdown Table</strong></summary>
+
+```golang
+    t.RenderMarkdown()
+```
+to get:
+```markdown
+| # | First Name | Last Name | Salary |  |
+| ---:| --- | --- | ---:| --- |
+| 1 | Arya | Stark | 3000 |  |
+| 20 | Jon | Snow | 2000 | You know nothing, Jon Snow! |
+| 300 | Tyrion | Lannister | 5000 |  |
+|  |  | Total | 10000 |  |
+```
+
+</details>
+
+---
diff --git a/table/README.md b/table/README.md
index 386def1..8d68c30 100644
--- a/table/README.md
+++ b/table/README.md
@@ -22,7 +22,7 @@ Pretty-print tables into ASCII/Unicode strings.
 A demonstration of all the capabilities can be found here:
 [../cmd/demo-table](../cmd/demo-table)
 
-If you want very specific examples, look at the [Examples](#examples) section.
+If you want very specific examples, look at the [EXAMPLES.md](EXAMPLES.md) file.
 
 ## Features
 
@@ -113,6 +113,7 @@ If you want very specific examples, look at the [Examples](#examples) section.
     - Enable/disable separators between rows
     - Render table with or without borders
     - Customize box-drawing characters
+      - Horizontal separators per section (title, header, rows, footer) using `BoxStyleHorizontal`
     - Title and caption styling options
     - HTML rendering options (CSS class, escaping, newlines, color conversion)
     - Bidirectional text support (`Style().Format.Direction`)
@@ -126,520 +127,3 @@ If you want very specific examples, look at the [Examples](#examples) section.
     - Markdown Table - Markdown-compatible format
     - TSV - Tab-separated values
   - Mirror output to an `io.Writer` (ex. `os.StdOut`) (`SetOutputMirror`)
-
-## Examples
-
-All the examples below are going to start with the following block, although
-nothing except a single Row is mandatory for the `Render()` function to render
-something:
-```golang
-package main
-
-import (
-    "os"
-
-    "github.com/jedib0t/go-pretty/v6/table"
-)
-
-func main() {
-    t := table.NewWriter()
-    t.SetOutputMirror(os.Stdout)
-    t.AppendHeader(table.Row{"#", "First Name", "Last Name", "Salary"})
-    t.AppendRows([]table.Row{
-        {1, "Arya", "Stark", 3000},
-        {20, "Jon", "Snow", 2000, "You know nothing, Jon Snow!"},
-    })
-    t.AppendSeparator()
-    t.AppendRow([]interface{}{300, "Tyrion", "Lannister", 5000})
-    t.AppendFooter(table.Row{"", "", "Total", 10000})
-    t.Render()
-}
-```
-Running the above will result in:
-```
-+-----+------------+-----------+--------+-----------------------------+
-|   # | FIRST NAME | LAST NAME | SALARY |                             |
-+-----+------------+-----------+--------+-----------------------------+
-|   1 | Arya       | Stark     |   3000 |                             |
-|  20 | Jon        | Snow      |   2000 | You know nothing, Jon Snow! |
-+-----+------------+-----------+--------+-----------------------------+
-| 300 | Tyrion     | Lannister |   5000 |                             |
-+-----+------------+-----------+--------+-----------------------------+
-|     |            | TOTAL     |  10000 |                             |
-+-----+------------+-----------+--------+-----------------------------+
-```
-
-### Styles
-
-You can customize almost every single thing about the table above. The previous
-example just defaulted to `StyleDefault` during `Render()`. You can use a
-ready-to-use style (as in [style.go](style.go)) or customize it as you want.
-
-### Ready-to-use Styles
-
-Table comes with a bunch of ready-to-use Styles that make the table look really
-good. Set or Change the style using:
-```golang
-    t.SetStyle(table.StyleLight)
-    t.Render()
-```
-to get:
-```
-┌─────┬────────────┬───────────┬────────┬─────────────────────────────┐
-│   # │ FIRST NAME │ LAST NAME │ SALARY │                             │
-├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
-│   1 │ Arya       │ Stark     │   3000 │                             │
-│  20 │ Jon        │ Snow      │   2000 │ You know nothing, Jon Snow! │
-├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
-│ 300 │ Tyrion     │ Lannister │   5000 │                             │
-├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
-│     │            │ TOTAL     │  10000 │                             │
-└─────┴────────────┴───────────┴────────┴─────────────────────────────┘
-```
-
-Or if you want to use a full-color mode, and don't care for boxes, use:
-```golang
-    t.SetStyle(table.StyleColoredBright)
-    t.Render()
-```
-to get:
-
-<img src="images/table-StyleColoredBright.png" width="640px" alt="Colored Table"/>
-
-#### Roll your own Style
-
-You can also roll your own style:
-```golang
-    t.SetStyle(table.Style{
-        Name: "myNewStyle",
-        Box: table.BoxStyle{
-            BottomLeft:       "\\",
-            BottomRight:      "/",
-            BottomSeparator:  "v",
-            Left:             "[",
-            LeftSeparator:    "{",
-            MiddleHorizontal: "-",
-            MiddleSeparator:  "+",
-            MiddleVertical:   "|",
-            PaddingLeft:      "<",
-            PaddingRight:     ">",
-            Right:            "]",
-            RightSeparator:   "}",
-            TopLeft:          "(",
-            TopRight:         ")",
-            TopSeparator:     "^",
-            UnfinishedRow:    " ~~~",
-        },
-        Color: table.ColorOptions{
-            IndexColumn:     text.Colors{text.BgCyan, text.FgBlack},
-            Footer:          text.Colors{text.BgCyan, text.FgBlack},
-            Header:          text.Colors{text.BgHiCyan, text.FgBlack},
-            Row:             text.Colors{text.BgHiWhite, text.FgBlack},
-            RowAlternate:    text.Colors{text.BgWhite, text.FgBlack},
-        },
-        Format: table.FormatOptions{
-            Footer: text.FormatUpper,
-            Header: text.FormatUpper,
-            Row:    text.FormatDefault,
-        },
-        Options: table.Options{
-            DrawBorder:      true,
-            SeparateColumns: true,
-            SeparateFooter:  true,
-            SeparateHeader:  true,
-            SeparateRows:    false,
-        },
-    })
-```
-
-Or you can use one of the ready-to-use Styles, and just make a few tweaks:
-```golang
-    t.SetStyle(table.StyleLight)
-    t.Style().Color.Header = text.Colors{text.BgHiCyan, text.FgBlack}
-    t.Style().Color.IndexColumn = text.Colors{text.BgHiCyan, text.FgBlack}
-    t.Style().Format.Footer = text.FormatLower
-    t.Style().Options.DrawBorder = false
-```
-
-### Auto-Merge
-
-You can auto-merge cells horizontally and vertically, but you have request for
-it specifically for each row/column using `RowConfig` or `ColumnConfig`.
-
-```golang
-    rowConfigAutoMerge := table.RowConfig{AutoMerge: true}
-
-    t := table.NewWriter()
-    t.AppendHeader(table.Row{"Node IP", "Pods", "Namespace", "Container", "RCE", "RCE"}, rowConfigAutoMerge)
-    t.AppendHeader(table.Row{"Node IP", "Pods", "Namespace", "Container", "EXE", "RUN"})
-    t.AppendRow(table.Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 1", "Y", "Y"}, rowConfigAutoMerge)
-    t.AppendRow(table.Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 2", "Y", "N"}, rowConfigAutoMerge)
-    t.AppendRow(table.Row{"1.1.1.1", "Pod 1A", "NS 1B", "C 3", "N", "N"}, rowConfigAutoMerge)
-    t.AppendRow(table.Row{"1.1.1.1", "Pod 1B", "NS 2", "C 4", "N", "N"}, rowConfigAutoMerge)
-    t.AppendRow(table.Row{"1.1.1.1", "Pod 1B", "NS 2", "C 5", "Y", "N"}, rowConfigAutoMerge)
-    t.AppendRow(table.Row{"2.2.2.2", "Pod 2", "NS 3", "C 6", "Y", "Y"}, rowConfigAutoMerge)
-    t.AppendRow(table.Row{"2.2.2.2", "Pod 2", "NS 3", "C 7", "Y", "Y"}, rowConfigAutoMerge)
-    t.AppendFooter(table.Row{"", "", "", 7, 5, 3})
-    t.SetAutoIndex(true)
-    t.SetColumnConfigs([]table.ColumnConfig{
-        {Number: 1, AutoMerge: true},
-        {Number: 2, AutoMerge: true},
-        {Number: 3, AutoMerge: true},
-        {Number: 4, AutoMerge: true},
-        {Number: 5, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter},
-        {Number: 6, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter},
-    })
-    t.SetStyle(table.StyleLight)
-    t.Style().Options.SeparateRows = true
-    fmt.Println(t.Render())
-```
-to get:
-```
-┌───┬─────────┬────────┬───────────┬───────────┬───────────┐
-│   │ NODE IP │ PODS   │ NAMESPACE │ CONTAINER │    RCE    │
-│   │         │        │           │           ├─────┬─────┤
-│   │         │        │           │           │ EXE │ RUN │
-├───┼─────────┼────────┼───────────┼───────────┼─────┴─────┤
-│ 1 │ 1.1.1.1 │ Pod 1A │ NS 1A     │ C 1       │     Y     │
-├───┤         │        │           ├───────────┼─────┬─────┤
-│ 2 │         │        │           │ C 2       │  Y  │  N  │
-├───┤         │        ├───────────┼───────────┼─────┴─────┤
-│ 3 │         │        │ NS 1B     │ C 3       │     N     │
-├───┤         ├────────┼───────────┼───────────┼───────────┤
-│ 4 │         │ Pod 1B │ NS 2      │ C 4       │     N     │
-├───┤         │        │           ├───────────┼─────┬─────┤
-│ 5 │         │        │           │ C 5       │  Y  │  N  │
-├───┼─────────┼────────┼───────────┼───────────┼─────┴─────┤
-│ 6 │ 2.2.2.2 │ Pod 2  │ NS 3      │ C 6       │     Y     │
-├───┤         │        │           ├───────────┼───────────┤
-│ 7 │         │        │           │ C 7       │     Y     │
-├───┼─────────┼────────┼───────────┼───────────┼─────┬─────┤
-│   │         │        │           │ 7         │  5  │  3  │
-└───┴─────────┴────────┴───────────┴───────────┴─────┴─────┘
-```
-
-### Paging
-
-You can limit the number of lines rendered in a single "Page". This logic
-can handle rows with multiple lines too. The recommended way is to use the
-`Pager()` interface:
-
-```golang
-    pager := t.Pager(PageSize(1))
-    pager.Render()  // Render first page
-    pager.Next()    // Move to next page and render
-    pager.Prev()    // Move to previous page and render
-    pager.GoTo(3)   // Jump to page 3
-    pager.Location() // Get current page number
-```
-
-Or use the deprecated `SetPageSize()` method for simple cases:
-```golang
-    t.SetPageSize(1)
-    t.Render()
-```
-to get:
-```
-+-----+------------+-----------+--------+-----------------------------+
-|   # | FIRST NAME | LAST NAME | SALARY |                             |
-+-----+------------+-----------+--------+-----------------------------+
-|   1 | Arya       | Stark     |   3000 |                             |
-+-----+------------+-----------+--------+-----------------------------+
-|     |            | TOTAL     |  10000 |                             |
-+-----+------------+-----------+--------+-----------------------------+
-
-+-----+------------+-----------+--------+-----------------------------+
-|   # | FIRST NAME | LAST NAME | SALARY |                             |
-+-----+------------+-----------+--------+-----------------------------+
-|  20 | Jon        | Snow      |   2000 | You know nothing, Jon Snow! |
-+-----+------------+-----------+--------+-----------------------------+
-|     |            | TOTAL     |  10000 |                             |
-+-----+------------+-----------+--------+-----------------------------+
-
-+-----+------------+-----------+--------+-----------------------------+
-|   # | FIRST NAME | LAST NAME | SALARY |                             |
-+-----+------------+-----------+--------+-----------------------------+
-| 300 | Tyrion     | Lannister |   5000 |                             |
-+-----+------------+-----------+--------+-----------------------------+
-|     |            | TOTAL     |  10000 |                             |
-+-----+------------+-----------+--------+-----------------------------+
-```
-
-### Sorting
-
-Sorting can be done on one or more columns. The following code will make the
-rows be sorted first by "First Name" and then by "Last Name" (in case of similar
-"First Name" entries).
-```golang
-    t.SortBy([]table.SortBy{
-	    {Name: "First Name", Mode: table.Asc},
-	    {Name: "Last Name", Mode: table.Asc},
-    })
-```
-
-#### Sort Modes
-
-The `Mode` field in `SortBy` supports various sorting modes:
-- `Asc` / `Dsc` - Alphabetical ascending/descending
-- `AscNumeric` / `DscNumeric` - Numerical ascending/descending
-- `AscAlphaNumeric` / `DscAlphaNumeric` - Alphabetical first, then numerical
-- `AscNumericAlpha` / `DscNumericAlpha` - Numerical first, then alphabetical
-
-You can also make sorting case-insensitive by setting `IgnoreCase: true`:
-```golang
-    t.SortBy([]table.SortBy{
-	    {Name: "First Name", Mode: table.Asc, IgnoreCase: true},
-    })
-```
-
-#### Custom Sorting
-
-For advanced sorting requirements, you can provide a custom comparison function
-using `CustomLess`. This function overrides the `Mode` and `IgnoreCase` settings
-and gives you full control over the sorting logic.
-
-The `CustomLess` function receives two string values (the cell contents converted
-to strings) and must return:
-- `-1` when the first value should come before the second
-- `0` when the values are considered equal (sorting continues to the next column)
-- `1` when the first value should come after the second
-
-<details>
-<summary>Example: Custom numeric sorting that handles string numbers correctly</summary>
-
-```golang
-    t.SortBy([]table.SortBy{
-	    {
-		    Number: 1,
-		    CustomLess: func(iStr string, jStr string) int {
-			    iNum, iErr := strconv.Atoi(iStr)
-			    jNum, jErr := strconv.Atoi(jStr)
-			    if iErr != nil || jErr != nil {
-				    // Fallback to string comparison if not numeric
-				    if iStr < jStr {
-					    return -1
-				    }
-				    if iStr > jStr {
-					    return 1
-				    }
-				    return 0
-			    }
-			    if iNum < jNum {
-				    return -1
-			    }
-			    if iNum > jNum {
-				    return 1
-			    }
-			    return 0
-		    },
-	    },
-    })
-```
-
-</details>
-
-<details>
-<summary>Example: Custom case-insensitive sorting with fallback to case-sensitive</summary>
-
-```golang
-    t.SortBy([]table.SortBy{
-	    {
-		    Name: "Name",
-		    CustomLess: func(iStr string, jStr string) int {
-			    iLower := strings.ToLower(iStr)
-			    jLower := strings.ToLower(jStr)
-			    if iLower < jLower {
-				    return -1
-			    }
-			    if iLower > jLower {
-				    return 1
-			    }
-			    // If case-insensitive equal, compare case-sensitive
-			    if iStr < jStr {
-				    return -1
-			    }
-			    if iStr > jStr {
-				    return 1
-			    }
-			    return 0
-		    },
-	    },
-    })
-```
-
-</details>
-
-<details>
-<summary>Example: Combining custom sorting with default sorting modes</summary>
-
-```golang
-    t.SortBy([]table.SortBy{
-	    {
-		    Number: 1,
-		    CustomLess: func(iStr string, jStr string) int {
-			    // Custom logic: "same" values come first
-			    if iStr == "same" && jStr != "same" {
-				    return -1
-			    }
-			    if iStr != "same" && jStr == "same" {
-				    return 1
-			    }
-			    return 0 // Equal, continue to next column
-		    },
-	    },
-	    {Number: 2, Mode: table.Asc},        // Default alphabetical sort
-	    {Number: 3, Mode: table.AscNumeric}, // Default numeric sort
-    })
-```
-
-</details>
-
-### Wrapping (or) Row/Column Width restrictions
-
-You can restrict the maximum (text) width for a Row:
-```golang
-    t.SetAllowedRowLength(50)
-    t.Render()
-```
-to get:
-```
-+-----+------------+-----------+--------+------- ~
-|   # | FIRST NAME | LAST NAME | SALARY |        ~
-+-----+------------+-----------+--------+------- ~
-|   1 | Arya       | Stark     |   3000 |        ~
-|  20 | Jon        | Snow      |   2000 | You kn ~
-+-----+------------+-----------+--------+------- ~
-| 300 | Tyrion     | Lannister |   5000 |        ~
-+-----+------------+-----------+--------+------- ~
-|     |            | TOTAL     |  10000 |        ~
-+-----+------------+-----------+--------+------- ~
-```
-
-### Column Control - Alignment, Colors, Width and more
-
-You can control a lot of things about individual cells/columns which overrides
-global properties/styles using the `SetColumnConfig()` interface:
-- Alignment (horizontal & vertical)
-- Colorization
-- Transform individual cells based on the content
-- Visibility
-- Width (minimum & maximum)
-
-```golang
-    nameTransformer := text.Transformer(func(val interface{}) string {
-        return text.Bold.Sprint(val)
-    })
-
-    t.SetColumnConfigs([]ColumnConfig{
-        {
-            Name:              "First Name",
-            Align:             text.AlignLeft,
-            AlignFooter:       text.AlignLeft,
-            AlignHeader:       text.AlignLeft,
-            Colors:            text.Colors{text.BgBlack, text.FgRed},
-            ColorsHeader:      text.Colors{text.BgRed, text.FgBlack, text.Bold},
-            ColorsFooter:      text.Colors{text.BgRed, text.FgBlack},
-            Hidden:            false,
-            Transformer:       nameTransformer,
-            TransformerFooter: nameTransformer,
-            TransformerHeader: nameTransformer,
-            VAlign:            text.VAlignMiddle,
-            VAlignFooter:      text.VAlignTop,
-            VAlignHeader:      text.VAlignBottom,
-            WidthMin:          6,
-            WidthMax:          64,
-        }
-    })
-```
-
-### Render As ...
-
-Tables can be rendered in other common formats such as:
-
-#### ... CSV
-
-```golang
-    t.RenderCSV()
-```
-to get:
-```
-,First Name,Last Name,Salary,
-1,Arya,Stark,3000,
-20,Jon,Snow,2000,"You know nothing\, Jon Snow!"
-300,Tyrion,Lannister,5000,
-,,Total,10000,
-```
-
-#### ... HTML Table
-
-```golang
-    t.Style().HTML = table.HTMLOptions{
-        CSSClass:             "game-of-thrones",
-        EmptyColumn:          "&nbsp;",
-        EscapeText:           true,
-        Newline:              "<br/>",
-        ConvertColorsToSpans: true, // Convert ANSI escape sequences to HTML <span> tags with CSS classes
-    }
-    t.RenderHTML()
-```
-to get:
-```html
-<table class="game-of-thrones">
-  <thead>
-  <tr>
-    <th align="right">#</th>
-    <th>First Name</th>
-    <th>Last Name</th>
-    <th align="right">Salary</th>
-    <th>&nbsp;</th>
-  </tr>
-  </thead>
-  <tbody>
-  <tr>
-    <td align="right">1</td>
-    <td>Arya</td>
-    <td>Stark</td>
-    <td align="right">3000</td>
-    <td>&nbsp;</td>
-  </tr>
-  <tr>
-    <td align="right">20</td>
-    <td>Jon</td>
-    <td>Snow</td>
-    <td align="right">2000</td>
-    <td>You know nothing, Jon Snow!</td>
-  </tr>
-  <tr>
-    <td align="right">300</td>
-    <td>Tyrion</td>
-    <td>Lannister</td>
-    <td align="right">5000</td>
-    <td>&nbsp;</td>
-  </tr>
-  </tbody>
-  <tfoot>
-  <tr>
-    <td align="right">&nbsp;</td>
-    <td>&nbsp;</td>
-    <td>Total</td>
-    <td align="right">10000</td>
-    <td>&nbsp;</td>
-  </tr>
-  </tfoot>
-</table>
-```
-
-#### ... Markdown Table
-
-```golang
-    t.RenderMarkdown()
-```
-to get:
-```markdown
-| # | First Name | Last Name | Salary |  |
-| ---:| --- | --- | ---:| --- |
-| 1 | Arya | Stark | 3000 |  |
-| 20 | Jon | Snow | 2000 | You know nothing, Jon Snow! |
-| 300 | Tyrion | Lannister | 5000 |  |
-|  |  | Total | 10000 |  |
-```
diff --git a/table/render.go b/table/render.go
index 80d84da..68d920e 100644
--- a/table/render.go
+++ b/table/render.go
@@ -20,7 +20,7 @@ import (
 //	│     │            │ TOTAL     │  10000 │                             │
 //	└─────┴────────────┴───────────┴────────┴─────────────────────────────┘
 func (t *Table) Render() string {
-	t.initForRender()
+	t.initForRender(renderModeDefault)
 
 	var out strings.Builder
 	if t.numColumns > 0 {
@@ -50,6 +50,7 @@ func (t *Table) Render() string {
 	return t.render(&out)
 }
 
+//gocyclo:ignore
 func (t *Table) renderColumn(out *strings.Builder, row rowStr, colIdx int, maxColumnLength int, hint renderHint) int {
 	numColumnsRendered := 1
 
@@ -93,11 +94,10 @@ func (t *Table) renderColumn(out *strings.Builder, row rowStr, colIdx int, maxCo
 			numColumnsRendered++
 		}
 	}
-	colStr = align.Apply(colStr, maxColumnLength)
 
 	// pad both sides of the column
 	if !hint.isSeparatorRow || (hint.isSeparatorRow && mergeVertically) {
-		colStr = t.style.Box.PaddingLeft + colStr + t.style.Box.PaddingRight
+		colStr = t.style.Box.PaddingLeft + align.Apply(colStr, maxColumnLength) + t.style.Box.PaddingRight
 	}
 
 	t.renderColumnColorized(out, colIdx, colStr, hint)
@@ -112,9 +112,9 @@ func (t *Table) renderColumnAutoIndex(out *strings.Builder, hint renderHint) {
 	if hint.isSeparatorRow {
 		numChars := t.autoIndexVIndexMaxLength + utf8.RuneCountInString(t.style.Box.PaddingLeft) +
 			utf8.RuneCountInString(t.style.Box.PaddingRight)
-		chars := t.style.Box.MiddleHorizontal
+		chars := t.style.Box.middleHorizontal(hint.separatorType)
 		if hint.isAutoIndexColumn && hint.isHeaderOrFooterSeparator() {
-			chars = text.RepeatAndTrim(" ", len(t.style.Box.MiddleHorizontal))
+			chars = text.RepeatAndTrim(" ", len(chars))
 		}
 		outAutoIndex.WriteString(text.RepeatAndTrim(chars, numChars))
 	} else {
@@ -304,8 +304,10 @@ func (t *Table) renderRowSeparator(out *strings.Builder, hint renderHint) {
 	} else if hint.isFooterRow && !t.style.Options.SeparateFooter {
 		return
 	}
+
 	hint.isSeparatorRow = true
-	t.renderLine(out, t.rowSeparator, hint)
+	separator := t.rowSeparatorStrings[hint.separatorType]
+	t.renderLine(out, t.rowSeparators[separator], hint)
 }
 
 func (t *Table) renderRows(out *strings.Builder, rows []rowStr, hint renderHint) {
@@ -316,8 +318,17 @@ func (t *Table) renderRows(out *strings.Builder, rows []rowStr, hint renderHint)
 		t.renderRow(out, row, hint)
 
 		if t.shouldSeparateRows(rowIdx, len(rows)) {
-			hint.isFirstRow = false
-			t.renderRowSeparator(out, hint)
+			hintSep := hint
+			hintSep.isFirstRow = false
+			hintSep.isSeparatorRow = true
+			if hintSep.isHeaderRow {
+				hintSep.separatorType = separatorTypeHeaderMiddle
+			} else if hintSep.isFooterRow {
+				hintSep.separatorType = separatorTypeFooterMiddle
+			} else {
+				hintSep.separatorType = separatorTypeRowMiddle
+			}
+			t.renderRowSeparator(out, hintSep)
 		}
 	}
 }
@@ -328,28 +339,41 @@ func (t *Table) renderRowsBorderBottom(out *strings.Builder) {
 			isBorderBottom: true,
 			isFooterRow:    true,
 			rowNumber:      len(t.rowsFooter),
+			separatorType:  separatorTypeFooterBottom,
 		})
 	} else {
 		t.renderRowSeparator(out, renderHint{
 			isBorderBottom: true,
 			isFooterRow:    false,
 			rowNumber:      len(t.rows),
+			separatorType:  separatorTypeRowBottom,
 		})
 	}
 }
 
 func (t *Table) renderRowsBorderTop(out *strings.Builder) {
+	st := separatorTypeHeaderTop
+	if t.title != "" {
+		st = separatorTypeTitleBottom
+	} else if len(t.rowsHeader) == 0 && !t.autoIndex {
+		st = separatorTypeRowTop
+	}
+
 	if len(t.rowsHeader) > 0 || t.autoIndex {
 		t.renderRowSeparator(out, renderHint{
-			isBorderTop: true,
-			isHeaderRow: true,
-			rowNumber:   0,
+			isBorderTop:    true,
+			isHeaderRow:    true,
+			isSeparatorRow: true,
+			rowNumber:      0,
+			separatorType:  st,
 		})
 	} else {
 		t.renderRowSeparator(out, renderHint{
-			isBorderTop: true,
-			isHeaderRow: false,
-			rowNumber:   0,
+			isBorderTop:    true,
+			isHeaderRow:    false,
+			isSeparatorRow: true,
+			rowNumber:      0,
+			separatorType:  st,
 		})
 	}
 }
@@ -360,6 +384,7 @@ func (t *Table) renderRowsFooter(out *strings.Builder) {
 			isFooterRow:    true,
 			isFirstRow:     true,
 			isSeparatorRow: true,
+			separatorType:  separatorTypeFooterTop,
 		})
 		t.renderRows(out, t.rowsFooter, renderHint{isFooterRow: true})
 	}
@@ -367,7 +392,12 @@ func (t *Table) renderRowsFooter(out *strings.Builder) {
 
 func (t *Table) renderRowsHeader(out *strings.Builder) {
 	if len(t.rowsHeader) > 0 || t.autoIndex {
-		hintSeparator := renderHint{isHeaderRow: true, isLastRow: true, isSeparatorRow: true}
+		hintSeparator := renderHint{
+			isHeaderRow:    true,
+			isLastRow:      true,
+			isSeparatorRow: true,
+			separatorType:  separatorTypeHeaderMiddle,
+		}
 
 		if len(t.rowsHeader) > 0 {
 			t.renderRows(out, t.rowsHeader, renderHint{isHeaderRow: true})
@@ -376,6 +406,8 @@ func (t *Table) renderRowsHeader(out *strings.Builder) {
 			t.renderRow(out, t.getAutoIndexColumnIDs(), renderHint{isAutoIndexRow: true, isHeaderRow: true})
 			hintSeparator.rowNumber = 1
 		}
+
+		hintSeparator.separatorType = separatorTypeHeaderBottom
 		t.renderRowSeparator(out, hintSeparator)
 	}
 }
@@ -393,8 +425,9 @@ func (t *Table) renderTitle(out *strings.Builder) {
 		}
 		if t.style.Options.DrawBorder {
 			lenBorder := rowLength - text.StringWidthWithoutEscSequences(t.style.Box.TopLeft+t.style.Box.TopRight)
+			middleHorizontal := t.style.Box.middleHorizontal(separatorTypeTitleTop)
 			out.WriteString(colorsBorder.Sprint(t.style.Box.TopLeft))
-			out.WriteString(colorsBorder.Sprint(text.RepeatAndTrim(t.style.Box.MiddleHorizontal, lenBorder)))
+			out.WriteString(colorsBorder.Sprint(text.RepeatAndTrim(middleHorizontal, lenBorder)))
 			out.WriteString(colorsBorder.Sprint(t.style.Box.TopRight))
 		}
 
diff --git a/table/render_csv.go b/table/render_csv.go
index 831194e..e9ea5ef 100644
--- a/table/render_csv.go
+++ b/table/render_csv.go
@@ -14,7 +14,7 @@ import (
 //	300,Tyrion,Lannister,5000,
 //	,,Total,10000,
 func (t *Table) RenderCSV() string {
-	t.initForRender()
+	t.initForRender(renderModeCSV)
 
 	var out strings.Builder
 	if t.numColumns > 0 {
diff --git a/table/render_hint.go b/table/render_hint.go
index e46cdc8..167cf88 100644
--- a/table/render_hint.go
+++ b/table/render_hint.go
@@ -15,6 +15,7 @@ type renderHint struct {
 	isTitleRow        bool // title row?
 	rowLineNumber     int  // the line number for a multi-line row
 	rowNumber         int  // the row number/index
+	separatorType     separatorType
 }
 
 func (h *renderHint) isBorderOrSeparator() bool {
@@ -37,3 +38,13 @@ func (h *renderHint) isHeaderOrFooterSeparator() bool {
 func (h *renderHint) isLastLineOfLastRow() bool {
 	return h.isLastLineOfRow && h.isLastRow
 }
+
+type renderMode string
+
+const (
+	renderModeDefault  renderMode = "default"
+	renderModeCSV      renderMode = "csv"
+	renderModeMarkdown renderMode = "markdown"
+	renderModeTSV      renderMode = "tsv"
+	renderModeHTML     renderMode = "html"
+)
diff --git a/table/render_html.go b/table/render_html.go
index 1057207..fec68f0 100644
--- a/table/render_html.go
+++ b/table/render_html.go
@@ -60,7 +60,7 @@ const (
 //	  </tfoot>
 //	</table>
 func (t *Table) RenderHTML() string {
-	t.initForRender()
+	t.initForRender(renderModeHTML)
 
 	var out strings.Builder
 	if t.numColumns > 0 {
diff --git a/table/render_init.go b/table/render_init.go
index f4c5817..3a36d21 100644
--- a/table/render_init.go
+++ b/table/render_init.go
@@ -153,7 +153,9 @@ func (t *Table) reBalanceMaxMergedColumnLengths() {
 	}
 }
 
-func (t *Table) initForRender() {
+func (t *Table) initForRender(mode renderMode) {
+	t.renderMode = mode
+
 	// pick a default style if none was set until now
 	t.Style()
 
@@ -348,10 +350,57 @@ func (t *Table) initForRenderRowPainterColors() {
 }
 
 func (t *Table) initForRenderRowSeparator() {
-	t.rowSeparator = make(rowStr, t.numColumns)
-	for colIdx, maxColumnLength := range t.maxColumnLengths {
-		maxColumnLength += text.StringWidthWithoutEscSequences(t.style.Box.PaddingLeft + t.style.Box.PaddingRight)
-		t.rowSeparator[colIdx] = text.RepeatAndTrim(t.style.Box.MiddleHorizontal, maxColumnLength)
+	// this is needed only for default render mode
+	if t.renderMode != renderModeDefault {
+		return
+	}
+
+	// init the separatorType -> separator-string map
+	t.initForRenderRowSeparatorStrings()
+
+	// init the separator-string -> separator-row map
+	t.rowSeparators = make(map[string]rowStr, len(t.rowSeparatorStrings))
+	paddingLength := text.StringWidthWithoutEscSequences(t.style.Box.PaddingLeft + t.style.Box.PaddingRight)
+	for _, separator := range t.rowSeparatorStrings {
+		t.rowSeparators[separator] = make(rowStr, t.numColumns)
+		for colIdx, maxColumnLength := range t.maxColumnLengths {
+			t.rowSeparators[separator][colIdx] = text.RepeatAndTrim(separator, maxColumnLength+paddingLength)
+		}
+	}
+}
+
+func (t *Table) initForRenderRowSeparatorStrings() {
+	// allocate and init only the separators that are needed
+	t.rowSeparatorStrings = make(map[separatorType]string)
+	addSeparatorType := func(st separatorType) {
+		t.rowSeparatorStrings[st] = t.style.Box.middleHorizontal(st)
+	}
+
+	// for other render modes, we need all the separators
+	if t.title != "" {
+		addSeparatorType(separatorTypeTitleTop)
+		addSeparatorType(separatorTypeTitleBottom)
+	}
+	if len(t.rowsHeader) > 0 || t.autoIndex {
+		addSeparatorType(separatorTypeHeaderTop)
+		addSeparatorType(separatorTypeHeaderBottom)
+		if len(t.rowsHeader) > 1 {
+			addSeparatorType(separatorTypeHeaderMiddle)
+		}
+	}
+	if len(t.rows) > 0 {
+		addSeparatorType(separatorTypeRowTop)
+		addSeparatorType(separatorTypeRowBottom)
+		if len(t.rows) > 1 {
+			addSeparatorType(separatorTypeRowMiddle)
+		}
+	}
+	if len(t.rowsFooter) > 0 || t.autoIndex {
+		addSeparatorType(separatorTypeFooterTop)
+		addSeparatorType(separatorTypeFooterBottom)
+		if len(t.rowsFooter) > 1 {
+			addSeparatorType(separatorTypeFooterMiddle)
+		}
 	}
 }
 
@@ -409,7 +458,7 @@ func (t *Table) reset() {
 	t.maxRowLength = 0
 	t.numColumns = 0
 	t.numLinesRendered = 0
-	t.rowSeparator = nil
+	t.rowSeparators = nil
 	t.rows = nil
 	t.rowsColors = nil
 	t.rowsFooter = nil
diff --git a/table/render_markdown.go b/table/render_markdown.go
index adf573f..80cdcc0 100644
--- a/table/render_markdown.go
+++ b/table/render_markdown.go
@@ -14,7 +14,7 @@ import (
 //	| 300 | Tyrion | Lannister | 5000 |  |
 //	|  |  | Total | 10000 |  |
 func (t *Table) RenderMarkdown() string {
-	t.initForRender()
+	t.initForRender(renderModeMarkdown)
 
 	var out strings.Builder
 	if t.numColumns > 0 {
@@ -47,19 +47,15 @@ func (t *Table) markdownRenderRow(out *strings.Builder, row rowStr, hint renderH
 	for colIdx := 0; colIdx < t.numColumns; colIdx++ {
 		t.markdownRenderRowAutoIndex(out, colIdx, hint)
 
-		if hint.isSeparatorRow {
-			out.WriteString(t.getAlign(colIdx, hint).MarkdownProperty())
-		} else {
-			var colStr string
-			if colIdx < len(row) {
-				colStr = row[colIdx]
-			}
-			out.WriteRune(' ')
-			colStr = strings.ReplaceAll(colStr, "|", "\\|")
-			colStr = strings.ReplaceAll(colStr, "\n", "<br/>")
-			out.WriteString(colStr)
-			out.WriteRune(' ')
+		var colStr string
+		if colIdx < len(row) {
+			colStr = row[colIdx]
 		}
+		out.WriteRune(' ')
+		colStr = strings.ReplaceAll(colStr, "|", "\\|")
+		colStr = strings.ReplaceAll(colStr, "\n", "<br/>")
+		out.WriteString(colStr)
+		out.WriteRune(' ')
 		out.WriteRune('|')
 	}
 }
@@ -83,7 +79,7 @@ func (t *Table) markdownRenderRows(out *strings.Builder, rows []rowStr, hint ren
 			t.markdownRenderRow(out, row, hint)
 
 			if idx == len(rows)-1 && hint.isHeaderRow {
-				t.markdownRenderRow(out, t.rowSeparator, renderHint{isSeparatorRow: true})
+				t.markdownRenderSeparator(out, renderHint{isSeparatorRow: true})
 			}
 		}
 	}
@@ -101,6 +97,21 @@ func (t *Table) markdownRenderRowsHeader(out *strings.Builder) {
 	}
 }
 
+func (t *Table) markdownRenderSeparator(out *strings.Builder, hint renderHint) {
+	// when working on line number 2 or more, insert a newline first
+	if out.Len() > 0 {
+		out.WriteRune('\n')
+	}
+
+	out.WriteRune('|')
+	for colIdx := 0; colIdx < t.numColumns; colIdx++ {
+		t.markdownRenderRowAutoIndex(out, colIdx, hint)
+
+		out.WriteString(t.getAlign(colIdx, hint).MarkdownProperty())
+		out.WriteRune('|')
+	}
+}
+
 func (t *Table) markdownRenderTitle(out *strings.Builder) {
 	if t.title != "" {
 		out.WriteString("# ")
diff --git a/table/render_tsv.go b/table/render_tsv.go
index 67f1e7f..312f365 100644
--- a/table/render_tsv.go
+++ b/table/render_tsv.go
@@ -6,7 +6,7 @@ import (
 )
 
 func (t *Table) RenderTSV() string {
-	t.initForRender()
+	t.initForRender(renderModeTSV)
 
 	var out strings.Builder
 
diff --git a/table/style.go b/table/style.go
index 2534fac..17f5abf 100644
--- a/table/style.go
+++ b/table/style.go
@@ -341,217 +341,6 @@ var (
 	}
 )
 
-// BoxStyle defines the characters/strings to use to render the borders and
-// separators for the Table.
-type BoxStyle struct {
-	BottomLeft       string
-	BottomRight      string
-	BottomSeparator  string
-	EmptySeparator   string
-	Left             string
-	LeftSeparator    string
-	MiddleHorizontal string
-	MiddleSeparator  string
-	MiddleVertical   string
-	PaddingLeft      string
-	PaddingRight     string
-	PageSeparator    string
-	Right            string
-	RightSeparator   string
-	TopLeft          string
-	TopRight         string
-	TopSeparator     string
-	UnfinishedRow    string
-}
-
-var (
-	// StyleBoxDefault defines a Boxed-Table like below:
-	//  +-----+------------+-----------+--------+-----------------------------+
-	//  |   # | FIRST NAME | LAST NAME | SALARY |                             |
-	//  +-----+------------+-----------+--------+-----------------------------+
-	//  |   1 | Arya       | Stark     |   3000 |                             |
-	//  |  20 | Jon        | Snow      |   2000 | You know nothing, Jon Snow! |
-	//  | 300 | Tyrion     | Lannister |   5000 |                             |
-	//  +-----+------------+-----------+--------+-----------------------------+
-	//  |     |            | TOTAL     |  10000 |                             |
-	//  +-----+------------+-----------+--------+-----------------------------+
-	StyleBoxDefault = BoxStyle{
-		BottomLeft:       "+",
-		BottomRight:      "+",
-		BottomSeparator:  "+",
-		EmptySeparator:   " ",
-		Left:             "|",
-		LeftSeparator:    "+",
-		MiddleHorizontal: "-",
-		MiddleSeparator:  "+",
-		MiddleVertical:   "|",
-		PaddingLeft:      " ",
-		PaddingRight:     " ",
-		PageSeparator:    "\n",
-		Right:            "|",
-		RightSeparator:   "+",
-		TopLeft:          "+",
-		TopRight:         "+",
-		TopSeparator:     "+",
-		UnfinishedRow:    " ~",
-	}
-
-	// StyleBoxBold defines a Boxed-Table like below:
-	//  ┏━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
-	//  ┃   # ┃ FIRST NAME ┃ LAST NAME ┃ SALARY ┃                             ┃
-	//  ┣━━━━━╋━━━━━━━━━━━━╋━━━━━━━━━━━╋━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
-	//  ┃   1 ┃ Arya       ┃ Stark     ┃   3000 ┃                             ┃
-	//  ┃  20 ┃ Jon        ┃ Snow      ┃   2000 ┃ You know nothing, Jon Snow! ┃
-	//  ┃ 300 ┃ Tyrion     ┃ Lannister ┃   5000 ┃                             ┃
-	//  ┣━━━━━╋━━━━━━━━━━━━╋━━━━━━━━━━━╋━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
-	//  ┃     ┃            ┃ TOTAL     ┃  10000 ┃                             ┃
-	//  ┗━━━━━┻━━━━━━━━━━━━┻━━━━━━━━━━━┻━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
-	StyleBoxBold = BoxStyle{
-		BottomLeft:       "┗",
-		BottomRight:      "┛",
-		BottomSeparator:  "┻",
-		EmptySeparator:   " ",
-		Left:             "┃",
-		LeftSeparator:    "┣",
-		MiddleHorizontal: "━",
-		MiddleSeparator:  "╋",
-		MiddleVertical:   "┃",
-		PaddingLeft:      " ",
-		PaddingRight:     " ",
-		PageSeparator:    "\n",
-		Right:            "┃",
-		RightSeparator:   "┫",
-		TopLeft:          "┏",
-		TopRight:         "┓",
-		TopSeparator:     "┳",
-		UnfinishedRow:    " ≈",
-	}
-
-	// StyleBoxDouble defines a Boxed-Table like below:
-	//  ╔═════╦════════════╦═══════════╦════════╦═════════════════════════════╗
-	//  ║   # ║ FIRST NAME ║ LAST NAME ║ SALARY ║                             ║
-	//  ╠═════╬════════════╬═══════════╬════════╬═════════════════════════════╣
-	//  ║   1 ║ Arya       ║ Stark     ║   3000 ║                             ║
-	//  ║  20 ║ Jon        ║ Snow      ║   2000 ║ You know nothing, Jon Snow! ║
-	//  ║ 300 ║ Tyrion     ║ Lannister ║   5000 ║                             ║
-	//  ╠═════╬════════════╬═══════════╬════════╬═════════════════════════════╣
-	//  ║     ║            ║ TOTAL     ║  10000 ║                             ║
-	//  ╚═════╩════════════╩═══════════╩════════╩═════════════════════════════╝
-	StyleBoxDouble = BoxStyle{
-		BottomLeft:       "╚",
-		BottomRight:      "╝",
-		BottomSeparator:  "╩",
-		EmptySeparator:   " ",
-		Left:             "║",
-		LeftSeparator:    "╠",
-		MiddleHorizontal: "═",
-		MiddleSeparator:  "╬",
-		MiddleVertical:   "║",
-		PaddingLeft:      " ",
-		PaddingRight:     " ",
-		PageSeparator:    "\n",
-		Right:            "║",
-		RightSeparator:   "╣",
-		TopLeft:          "╔",
-		TopRight:         "╗",
-		TopSeparator:     "╦",
-		UnfinishedRow:    " ≈",
-	}
-
-	// StyleBoxLight defines a Boxed-Table like below:
-	//  ┌─────┬────────────┬───────────┬────────┬─────────────────────────────┐
-	//  │   # │ FIRST NAME │ LAST NAME │ SALARY │                             │
-	//  ├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
-	//  │   1 │ Arya       │ Stark     │   3000 │                             │
-	//  │  20 │ Jon        │ Snow      │   2000 │ You know nothing, Jon Snow! │
-	//  │ 300 │ Tyrion     │ Lannister │   5000 │                             │
-	//  ├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
-	//  │     │            │ TOTAL     │  10000 │                             │
-	//  └─────┴────────────┴───────────┴────────┴─────────────────────────────┘
-	StyleBoxLight = BoxStyle{
-		BottomLeft:       "└",
-		BottomRight:      "┘",
-		BottomSeparator:  "┴",
-		EmptySeparator:   " ",
-		Left:             "│",
-		LeftSeparator:    "├",
-		MiddleHorizontal: "─",
-		MiddleSeparator:  "┼",
-		MiddleVertical:   "│",
-		PaddingLeft:      " ",
-		PaddingRight:     " ",
-		PageSeparator:    "\n",
-		Right:            "│",
-		RightSeparator:   "┤",
-		TopLeft:          "┌",
-		TopRight:         "┐",
-		TopSeparator:     "┬",
-		UnfinishedRow:    " ≈",
-	}
-
-	// StyleBoxRounded defines a Boxed-Table like below:
-	//  ╭─────┬────────────┬───────────┬────────┬─────────────────────────────╮
-	//  │   # │ FIRST NAME │ LAST NAME │ SALARY │                             │
-	//  ├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
-	//  │   1 │ Arya       │ Stark     │   3000 │                             │
-	//  │  20 │ Jon        │ Snow      │   2000 │ You know nothing, Jon Snow! │
-	//  │ 300 │ Tyrion     │ Lannister │   5000 │                             │
-	//  ├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
-	//  │     │            │ TOTAL     │  10000 │                             │
-	//  ╰─────┴────────────┴───────────┴────────┴─────────────────────────────╯
-	StyleBoxRounded = BoxStyle{
-		BottomLeft:       "╰",
-		BottomRight:      "╯",
-		BottomSeparator:  "┴",
-		EmptySeparator:   " ",
-		Left:             "│",
-		LeftSeparator:    "├",
-		MiddleHorizontal: "─",
-		MiddleSeparator:  "┼",
-		MiddleVertical:   "│",
-		PaddingLeft:      " ",
-		PaddingRight:     " ",
-		PageSeparator:    "\n",
-		Right:            "│",
-		RightSeparator:   "┤",
-		TopLeft:          "╭",
-		TopRight:         "╮",
-		TopSeparator:     "┬",
-		UnfinishedRow:    " ≈",
-	}
-
-	// styleBoxTest defines a Boxed-Table like below:
-	//  (-----^------------^-----------^--------^-----------------------------)
-	//  [<  #>|<FIRST NAME>|<LAST NAME>|<SALARY>|<                           >]
-	//  {-----+------------+-----------+--------+-----------------------------}
-	//  [<  1>|<Arya      >|<Stark    >|<  3000>|<                           >]
-	//  [< 20>|<Jon       >|<Snow     >|<  2000>|<You know nothing, Jon Snow!>]
-	//  [<300>|<Tyrion    >|<Lannister>|<  5000>|<                           >]
-	//  {-----+------------+-----------+--------+-----------------------------}
-	//  [<   >|<          >|<TOTAL    >|< 10000>|<                           >]
-	//  \-----v------------v-----------v--------v-----------------------------/
-	styleBoxTest = BoxStyle{
-		BottomLeft:       "\\",
-		BottomRight:      "/",
-		BottomSeparator:  "v",
-		EmptySeparator:   " ",
-		Left:             "[",
-		LeftSeparator:    "{",
-		MiddleHorizontal: "--",
-		MiddleSeparator:  "+",
-		MiddleVertical:   "|",
-		PaddingLeft:      "<",
-		PaddingRight:     ">",
-		PageSeparator:    "\n",
-		Right:            "]",
-		RightSeparator:   "}",
-		TopLeft:          "(",
-		TopRight:         ")",
-		TopSeparator:     "^",
-		UnfinishedRow:    " ~~~",
-	}
-)
-
 // ColorOptions defines the ANSI colors to use for parts of the Table.
 type ColorOptions struct {
 	Border       text.Colors // borders (if nil, uses one of the below)
diff --git a/table/style_box.go b/table/style_box.go
new file mode 100644
index 0000000..e54f3c6
--- /dev/null
+++ b/table/style_box.go
@@ -0,0 +1,303 @@
+package table
+
+// BoxStyle defines the characters/strings to use to render the borders and
+// separators for the Table.
+type BoxStyle struct {
+	BottomLeft       string
+	BottomRight      string
+	BottomSeparator  string
+	EmptySeparator   string
+	Left             string
+	LeftSeparator    string
+	MiddleHorizontal string
+	MiddleSeparator  string
+	MiddleVertical   string
+	PaddingLeft      string
+	PaddingRight     string
+	PageSeparator    string
+	Right            string
+	RightSeparator   string
+	TopLeft          string
+	TopRight         string
+	TopSeparator     string
+	UnfinishedRow    string
+
+	// Horizontal lets you customize the horizontal lines for the Table
+	// in a more granular way than the MiddleHorizontal string. Setting
+	// this to a non-nil value will override MiddleHorizontal.
+	Horizontal *BoxStyleHorizontal
+}
+
+// BoxStyleHorizontal defines the characters/strings to use to render the
+// horizontal lines for the Table.
+type BoxStyleHorizontal struct {
+	TitleTop     string
+	TitleBottom  string // overrides HeaderTop/RowTop
+	HeaderTop    string
+	HeaderMiddle string
+	HeaderBottom string // overrides RowTop
+	RowTop       string
+	RowMiddle    string
+	RowBottom    string
+	FooterTop    string // overrides RowBottom
+	FooterMiddle string
+	FooterBottom string
+}
+
+// NewBoxStyleHorizontal creates a new BoxStyleHorizontal with the given
+// horizontal string.
+func NewBoxStyleHorizontal(horizontal string) *BoxStyleHorizontal {
+	return &BoxStyleHorizontal{
+		TitleTop:     horizontal,
+		TitleBottom:  horizontal,
+		HeaderTop:    horizontal,
+		HeaderMiddle: horizontal,
+		HeaderBottom: horizontal,
+		RowTop:       horizontal,
+		RowMiddle:    horizontal,
+		RowBottom:    horizontal,
+		FooterTop:    horizontal,
+		FooterMiddle: horizontal,
+		FooterBottom: horizontal,
+	}
+}
+
+var (
+	// StyleBoxDefault defines a Boxed-Table like below:
+	//  +-----+------------+-----------+--------+-----------------------------+
+	//  |   # | FIRST NAME | LAST NAME | SALARY |                             |
+	//  +-----+------------+-----------+--------+-----------------------------+
+	//  |   1 | Arya       | Stark     |   3000 |                             |
+	//  |  20 | Jon        | Snow      |   2000 | You know nothing, Jon Snow! |
+	//  | 300 | Tyrion     | Lannister |   5000 |                             |
+	//  +-----+------------+-----------+--------+-----------------------------+
+	//  |     |            | TOTAL     |  10000 |                             |
+	//  +-----+------------+-----------+--------+-----------------------------+
+	StyleBoxDefault = BoxStyle{
+		BottomLeft:       "+",
+		BottomRight:      "+",
+		BottomSeparator:  "+",
+		EmptySeparator:   " ",
+		Left:             "|",
+		LeftSeparator:    "+",
+		MiddleHorizontal: "-",
+		MiddleSeparator:  "+",
+		MiddleVertical:   "|",
+		PaddingLeft:      " ",
+		PaddingRight:     " ",
+		PageSeparator:    "\n",
+		Right:            "|",
+		RightSeparator:   "+",
+		TopLeft:          "+",
+		TopRight:         "+",
+		TopSeparator:     "+",
+		UnfinishedRow:    " ~",
+	}
+
+	// StyleBoxBold defines a Boxed-Table like below:
+	//  ┏━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
+	//  ┃   # ┃ FIRST NAME ┃ LAST NAME ┃ SALARY ┃                             ┃
+	//  ┣━━━━━╋━━━━━━━━━━━━╋━━━━━━━━━━━╋━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
+	//  ┃   1 ┃ Arya       ┃ Stark     ┃   3000 ┃                             ┃
+	//  ┃  20 ┃ Jon        ┃ Snow      ┃   2000 ┃ You know nothing, Jon Snow! ┃
+	//  ┃ 300 ┃ Tyrion     ┃ Lannister ┃   5000 ┃                             ┃
+	//  ┣━━━━━╋━━━━━━━━━━━━╋━━━━━━━━━━━╋━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
+	//  ┃     ┃            ┃ TOTAL     ┃  10000 ┃                             ┃
+	//  ┗━━━━━┻━━━━━━━━━━━━┻━━━━━━━━━━━┻━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
+	StyleBoxBold = BoxStyle{
+		BottomLeft:       "┗",
+		BottomRight:      "┛",
+		BottomSeparator:  "┻",
+		EmptySeparator:   " ",
+		Left:             "┃",
+		LeftSeparator:    "┣",
+		MiddleHorizontal: "━",
+		MiddleSeparator:  "╋",
+		MiddleVertical:   "┃",
+		PaddingLeft:      " ",
+		PaddingRight:     " ",
+		PageSeparator:    "\n",
+		Right:            "┃",
+		RightSeparator:   "┫",
+		TopLeft:          "┏",
+		TopRight:         "┓",
+		TopSeparator:     "┳",
+		UnfinishedRow:    " ≈",
+	}
+
+	// StyleBoxDouble defines a Boxed-Table like below:
+	//  ╔═════╦════════════╦═══════════╦════════╦═════════════════════════════╗
+	//  ║   # ║ FIRST NAME ║ LAST NAME ║ SALARY ║                             ║
+	//  ╠═════╬════════════╬═══════════╬════════╬═════════════════════════════╣
+	//  ║   1 ║ Arya       ║ Stark     ║   3000 ║                             ║
+	//  ║  20 ║ Jon        ║ Snow      ║   2000 ║ You know nothing, Jon Snow! ║
+	//  ║ 300 ║ Tyrion     ║ Lannister ║   5000 ║                             ║
+	//  ╠═════╬════════════╬═══════════╬════════╬═════════════════════════════╣
+	//  ║     ║            ║ TOTAL     ║  10000 ║                             ║
+	//  ╚═════╩════════════╩═══════════╩════════╩═════════════════════════════╝
+	StyleBoxDouble = BoxStyle{
+		BottomLeft:       "╚",
+		BottomRight:      "╝",
+		BottomSeparator:  "╩",
+		EmptySeparator:   " ",
+		Left:             "║",
+		LeftSeparator:    "╠",
+		MiddleHorizontal: "═",
+		MiddleSeparator:  "╬",
+		MiddleVertical:   "║",
+		PaddingLeft:      " ",
+		PaddingRight:     " ",
+		PageSeparator:    "\n",
+		Right:            "║",
+		RightSeparator:   "╣",
+		TopLeft:          "╔",
+		TopRight:         "╗",
+		TopSeparator:     "╦",
+		UnfinishedRow:    " ≈",
+	}
+
+	// StyleBoxLight defines a Boxed-Table like below:
+	//  ┌─────┬────────────┬───────────┬────────┬─────────────────────────────┐
+	//  │   # │ FIRST NAME │ LAST NAME │ SALARY │                             │
+	//  ├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
+	//  │   1 │ Arya       │ Stark     │   3000 │                             │
+	//  │  20 │ Jon        │ Snow      │   2000 │ You know nothing, Jon Snow! │
+	//  │ 300 │ Tyrion     │ Lannister │   5000 │                             │
+	//  ├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
+	//  │     │            │ TOTAL     │  10000 │                             │
+	//  └─────┴────────────┴───────────┴────────┴─────────────────────────────┘
+	StyleBoxLight = BoxStyle{
+		BottomLeft:       "└",
+		BottomRight:      "┘",
+		BottomSeparator:  "┴",
+		EmptySeparator:   " ",
+		Left:             "│",
+		LeftSeparator:    "├",
+		MiddleHorizontal: "─",
+		MiddleSeparator:  "┼",
+		MiddleVertical:   "│",
+		PaddingLeft:      " ",
+		PaddingRight:     " ",
+		PageSeparator:    "\n",
+		Right:            "│",
+		RightSeparator:   "┤",
+		TopLeft:          "┌",
+		TopRight:         "┐",
+		TopSeparator:     "┬",
+		UnfinishedRow:    " ≈",
+	}
+
+	// StyleBoxRounded defines a Boxed-Table like below:
+	//  ╭─────┬────────────┬───────────┬────────┬─────────────────────────────╮
+	//  │   # │ FIRST NAME │ LAST NAME │ SALARY │                             │
+	//  ├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
+	//  │   1 │ Arya       │ Stark     │   3000 │                             │
+	//  │  20 │ Jon        │ Snow      │   2000 │ You know nothing, Jon Snow! │
+	//  │ 300 │ Tyrion     │ Lannister │   5000 │                             │
+	//  ├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
+	//  │     │            │ TOTAL     │  10000 │                             │
+	//  ╰─────┴────────────┴───────────┴────────┴─────────────────────────────╯
+	StyleBoxRounded = BoxStyle{
+		BottomLeft:       "╰",
+		BottomRight:      "╯",
+		BottomSeparator:  "┴",
+		EmptySeparator:   " ",
+		Left:             "│",
+		LeftSeparator:    "├",
+		MiddleHorizontal: "─",
+		MiddleSeparator:  "┼",
+		MiddleVertical:   "│",
+		PaddingLeft:      " ",
+		PaddingRight:     " ",
+		PageSeparator:    "\n",
+		Right:            "│",
+		RightSeparator:   "┤",
+		TopLeft:          "╭",
+		TopRight:         "╮",
+		TopSeparator:     "┬",
+		UnfinishedRow:    " ≈",
+	}
+
+	// styleBoxTest defines a Boxed-Table like below:
+	//  (-----^------------^-----------^--------^-----------------------------)
+	//  [<  #>|<FIRST NAME>|<LAST NAME>|<SALARY>|<                           >]
+	//  {-----+------------+-----------+--------+-----------------------------}
+	//  [<  1>|<Arya      >|<Stark    >|<  3000>|<                           >]
+	//  [< 20>|<Jon       >|<Snow     >|<  2000>|<You know nothing, Jon Snow!>]
+	//  [<300>|<Tyrion    >|<Lannister>|<  5000>|<                           >]
+	//  {-----+------------+-----------+--------+-----------------------------}
+	//  [<   >|<          >|<TOTAL    >|< 10000>|<                           >]
+	//  \-----v------------v-----------v--------v-----------------------------/
+	styleBoxTest = BoxStyle{
+		BottomLeft:       "\\",
+		BottomRight:      "/",
+		BottomSeparator:  "v",
+		EmptySeparator:   " ",
+		Left:             "[",
+		LeftSeparator:    "{",
+		MiddleHorizontal: "--",
+		MiddleSeparator:  "+",
+		MiddleVertical:   "|",
+		PaddingLeft:      "<",
+		PaddingRight:     ">",
+		PageSeparator:    "\n",
+		Right:            "]",
+		RightSeparator:   "}",
+		TopLeft:          "(",
+		TopRight:         ")",
+		TopSeparator:     "^",
+		UnfinishedRow:    " ~~~",
+	}
+)
+
+type separatorType int
+
+const (
+	separatorTypeTitleTop separatorType = iota
+	separatorTypeTitleBottom
+	separatorTypeHeaderTop
+	separatorTypeHeaderMiddle
+	separatorTypeHeaderBottom
+	separatorTypeRowTop
+	separatorTypeRowMiddle
+	separatorTypeRowBottom
+	separatorTypeFooterTop
+	separatorTypeFooterMiddle
+	separatorTypeFooterBottom
+	separatorTypeCount // this should be the last value
+)
+
+func (bs *BoxStyle) ensureHorizontalInitialized() {
+	if bs.Horizontal == nil {
+		bs.Horizontal = NewBoxStyleHorizontal(bs.MiddleHorizontal)
+	}
+}
+
+func (bs *BoxStyle) middleHorizontal(st separatorType) string {
+	bs.ensureHorizontalInitialized()
+
+	switch st {
+	case separatorTypeTitleTop:
+		return bs.Horizontal.TitleTop
+	case separatorTypeTitleBottom:
+		return bs.Horizontal.TitleBottom
+	case separatorTypeHeaderTop:
+		return bs.Horizontal.HeaderTop
+	case separatorTypeHeaderMiddle:
+		return bs.Horizontal.HeaderMiddle
+	case separatorTypeHeaderBottom:
+		return bs.Horizontal.HeaderBottom
+	case separatorTypeRowTop:
+		return bs.Horizontal.RowTop
+	case separatorTypeRowBottom:
+		return bs.Horizontal.RowBottom
+	case separatorTypeFooterTop:
+		return bs.Horizontal.FooterTop
+	case separatorTypeFooterMiddle:
+		return bs.Horizontal.FooterMiddle
+	case separatorTypeFooterBottom:
+		return bs.Horizontal.FooterBottom
+	default:
+		return bs.Horizontal.RowMiddle
+	}
+}
diff --git a/table/table.go b/table/table.go
index c4127d4..838c34b 100644
--- a/table/table.go
+++ b/table/table.go
@@ -52,6 +52,8 @@ type Table struct {
 	outputMirror io.Writer
 	// pager controls how the output is separated into pages
 	pager pager
+	// renderMode contains the type of table to render
+	renderMode renderMode
 	// rows stores the rows that make up the body (in string form)
 	rows []rowStr
 	// rowsColors stores the text.Colors over-rides for each row as defined by
@@ -78,9 +80,11 @@ type Table struct {
 	rowPainter RowPainter
 	// rowPainterWithAttributes is same as rowPainter, but with attributes
 	rowPainterWithAttributes RowPainterWithAttributes
-	// rowSeparator is a dummy row that contains the separator columns (dashes
-	// that make up the separator between header/body/footer
-	rowSeparator rowStr
+	// rowSeparators contains the separator columns (dashes that make up the
+	// separators between title/header/body/footer
+	rowSeparators map[string]rowStr
+	// rowSeparatorStrings contains the separator strings for each separator type
+	rowSeparatorStrings map[separatorType]string
 	// separators is used to keep track of all rowIndices after which a
 	// separator has to be rendered
 	separators map[int]bool
@@ -507,13 +511,13 @@ func (t *Table) getColumnSeparator(row rowStr, colIdx int, hint renderHint) stri
 	if hint.isSeparatorRow {
 		if hint.isBorderTop {
 			if t.shouldMergeCellsHorizontallyBelow(row, colIdx, hint) {
-				separator = t.style.Box.MiddleHorizontal
+				separator = t.style.Box.middleHorizontal(hint.separatorType)
 			} else {
 				separator = t.style.Box.TopSeparator
 			}
 		} else if hint.isBorderBottom {
 			if t.shouldMergeCellsHorizontallyAbove(row, colIdx, hint) {
-				separator = t.style.Box.MiddleHorizontal
+				separator = t.style.Box.middleHorizontal(hint.separatorType)
 			} else {
 				separator = t.style.Box.BottomSeparator
 			}
@@ -533,7 +537,7 @@ func (t *Table) getColumnSeparatorNonBorder(mergeCellsAbove bool, mergeCellsBelo
 	}
 
 	mergeCurrCol := t.shouldMergeCellsVerticallyAbove(colIdx-1, hint)
-	return t.getColumnSeparatorNonBorderNonAutoIndex(mergeCellsAbove, mergeCellsBelow, mergeCurrCol, mergeNextCol)
+	return t.getColumnSeparatorNonBorderNonAutoIndex(mergeCellsAbove, mergeCellsBelow, mergeCurrCol, mergeNextCol, hint)
 }
 
 func (t *Table) getColumnSeparatorNonBorderAutoIndex(mergeNextCol bool, hint renderHint) string {
@@ -548,11 +552,11 @@ func (t *Table) getColumnSeparatorNonBorderAutoIndex(mergeNextCol bool, hint ren
 	return t.style.Box.MiddleSeparator
 }
 
-func (t *Table) getColumnSeparatorNonBorderNonAutoIndex(mergeCellsAbove bool, mergeCellsBelow bool, mergeCurrCol bool, mergeNextCol bool) string {
+func (t *Table) getColumnSeparatorNonBorderNonAutoIndex(mergeCellsAbove bool, mergeCellsBelow bool, mergeCurrCol bool, mergeNextCol bool, hint renderHint) string {
 	if mergeCellsAbove && mergeCellsBelow && mergeCurrCol && mergeNextCol {
 		return t.style.Box.EmptySeparator
 	} else if mergeCellsAbove && mergeCellsBelow {
-		return t.style.Box.MiddleHorizontal
+		return t.style.Box.middleHorizontal(hint.separatorType)
 	} else if mergeCellsAbove {
 		return t.style.Box.TopSeparator
 	} else if mergeCellsBelow {



Test patch:
diff --git a/table/render_test.go b/table/render_test.go
index 67cba48..f23ec7a 100644
--- a/table/render_test.go
+++ b/table/render_test.go
@@ -573,6 +573,223 @@ func TestTable_Render_BorderAndSeparators_Colored(t *testing.T) {
 	)
 }
 
+func TestTable_Render_Horizontal(t *testing.T) {
+	tw := NewWriter()
+	tw.AppendHeader(testHeader)
+	tw.AppendHeader(testHeader)
+	tw.AppendRows(testRows)
+	tw.AppendFooter(testFooter)
+	tw.AppendFooter(testFooter)
+	tw.SetStyle(StyleDefault)
+	tw.Style().Options.DrawBorder = true
+	tw.Style().Options.SeparateColumns = true
+	tw.Style().Options.SeparateFooter = true
+	tw.Style().Options.SeparateHeader = true
+	tw.Style().Options.SeparateRows = true
+
+	resetTable := func() {
+		tw.ResetHeaders()
+		tw.ResetRows()
+		tw.ResetFooters()
+	}
+
+	// Customize all BoxStyleHorizontal values with distinct characters
+	// This tests that all horizontal line customization options work correctly
+	tw.Style().Box.Horizontal = &BoxStyleHorizontal{
+		TitleTop:     "0",
+		TitleBottom:  "1",
+		HeaderTop:    "2",
+		HeaderMiddle: "3",
+		HeaderBottom: "4",
+		RowTop:       "5",
+		RowMiddle:    "6",
+		RowBottom:    "7",
+		FooterTop:    "8",
+		FooterMiddle: "9",
+		FooterBottom: "A",
+	}
+
+	t.Run("just rows", func(t *testing.T) {
+		resetTable()
+
+		tw.AppendRows(testRows)
+		tw.SetTitle("")
+
+		compareOutput(t, tw.Render(), `
++55555+55555555+55555555555+555555+55555555555555555555555555555+
+|   1 | Arya   | Stark     | 3000 |                             |
++66666+66666666+66666666666+666666+66666666666666666666666666666+
+|  20 | Jon    | Snow      | 2000 | You know nothing, Jon Snow! |
++66666+66666666+66666666666+666666+66666666666666666666666666666+
+| 300 | Tyrion | Lannister | 5000 |                             |
++77777+77777777+77777777777+777777+77777777777777777777777777777+`)
+	})
+
+	t.Run("headers & rows", func(t *testing.T) {
+		resetTable()
+
+		tw.AppendHeader(testHeader)
+		tw.AppendHeader(testHeader)
+		tw.AppendRows(testRows)
+		tw.SetTitle("")
+
+		compareOutput(t, tw.Render(), `
++22222+222222222222+22222222222+22222222+22222222222222222222222222222+
+|   # | FIRST NAME | LAST NAME | SALARY |                             |
++33333+333333333333+33333333333+33333333+33333333333333333333333333333+
+|   # | FIRST NAME | LAST NAME | SALARY |                             |
++44444+444444444444+44444444444+44444444+44444444444444444444444444444+
+|   1 | Arya       | Stark     |   3000 |                             |
++66666+666666666666+66666666666+66666666+66666666666666666666666666666+
+|  20 | Jon        | Snow      |   2000 | You know nothing, Jon Snow! |
++66666+666666666666+66666666666+66666666+66666666666666666666666666666+
+| 300 | Tyrion     | Lannister |   5000 |                             |
++77777+777777777777+77777777777+77777777+77777777777777777777777777777+`)
+	})
+
+	t.Run("headers & rows & footers", func(t *testing.T) {
+		resetTable()
+
+		tw.AppendHeader(testHeader)
+		tw.AppendHeader(testHeader)
+		tw.AppendRows(testRows)
+		tw.AppendFooter(testFooter)
+		tw.AppendFooter(testFooter)
+		tw.SetTitle("")
+
+		compareOutput(t, tw.Render(), `
++22222+222222222222+22222222222+22222222+22222222222222222222222222222+
+|   # | FIRST NAME | LAST NAME | SALARY |                             |
++33333+333333333333+33333333333+33333333+33333333333333333333333333333+
+|   # | FIRST NAME | LAST NAME | SALARY |                             |
++44444+444444444444+44444444444+44444444+44444444444444444444444444444+
+|   1 | Arya       | Stark     |   3000 |                             |
++66666+666666666666+66666666666+66666666+66666666666666666666666666666+
+|  20 | Jon        | Snow      |   2000 | You know nothing, Jon Snow! |
++66666+666666666666+66666666666+66666666+66666666666666666666666666666+
+| 300 | Tyrion     | Lannister |   5000 |                             |
++88888+888888888888+88888888888+88888888+88888888888888888888888888888+
+|     |            | TOTAL     |  10000 |                             |
++99999+999999999999+99999999999+99999999+99999999999999999999999999999+
+|     |            | TOTAL     |  10000 |                             |
++AAAAA+AAAAAAAAAAAA+AAAAAAAAAAA+AAAAAAAA+AAAAAAAAAAAAAAAAAAAAAAAAAAAAA+`)
+	})
+
+	t.Run("title & headers & rows & footers", func(t *testing.T) {
+		resetTable()
+
+		tw.AppendHeader(testHeader)
+		tw.AppendHeader(testHeader)
+		tw.AppendRows(testRows)
+		tw.AppendFooter(testFooter)
+		tw.AppendFooter(testFooter)
+		tw.SetTitle(testTitle1)
+
+		compareOutput(t, tw.Render(), `
++000000000000000000000000000000000000000000000000000000000000000000000+
+| Game of Thrones                                                     |
++11111+111111111111+11111111111+11111111+11111111111111111111111111111+
+|   # | FIRST NAME | LAST NAME | SALARY |                             |
++33333+333333333333+33333333333+33333333+33333333333333333333333333333+
+|   # | FIRST NAME | LAST NAME | SALARY |                             |
++44444+444444444444+44444444444+44444444+44444444444444444444444444444+
+|   1 | Arya       | Stark     |   3000 |                             |
++66666+666666666666+66666666666+66666666+66666666666666666666666666666+
+|  20 | Jon        | Snow      |   2000 | You know nothing, Jon Snow! |
++66666+666666666666+66666666666+66666666+66666666666666666666666666666+
+| 300 | Tyrion     | Lannister |   5000 |                             |
++88888+888888888888+88888888888+88888888+88888888888888888888888888888+
+|     |            | TOTAL     |  10000 |                             |
++99999+999999999999+99999999999+99999999+99999999999999999999999999999+
+|     |            | TOTAL     |  10000 |                             |
++AAAAA+AAAAAAAAAAAA+AAAAAAAAAAA+AAAAAAAA+AAAAAAAAAAAAAAAAAAAAAAAAAAAAA+`)
+	})
+
+	t.Run("title & rows & footers", func(t *testing.T) {
+		resetTable()
+
+		tw.AppendRows(testRows)
+		tw.AppendFooter(testFooter)
+		tw.AppendFooter(testFooter)
+		tw.SetTitle(testTitle1)
+
+		compareOutput(t, tw.Render(), `
++0000000000000000000000000000000000000000000000000000000000000000+
+| Game of Thrones                                                |
++11111+11111111+11111111111+1111111+11111111111111111111111111111+
+|   1 | Arya   | Stark     |  3000 |                             |
++66666+66666666+66666666666+6666666+66666666666666666666666666666+
+|  20 | Jon    | Snow      |  2000 | You know nothing, Jon Snow! |
++66666+66666666+66666666666+6666666+66666666666666666666666666666+
+| 300 | Tyrion | Lannister |  5000 |                             |
++88888+88888888+88888888888+8888888+88888888888888888888888888888+
+|     |        | TOTAL     | 10000 |                             |
++99999+99999999+99999999999+9999999+99999999999999999999999999999+
+|     |        | TOTAL     | 10000 |                             |
++AAAAA+AAAAAAAA+AAAAAAAAAAA+AAAAAAA+AAAAAAAAAAAAAAAAAAAAAAAAAAAAA+`)
+	})
+
+	t.Run("title & rows", func(t *testing.T) {
+		resetTable()
+
+		tw.AppendRows(testRows)
+		tw.SetTitle(testTitle1)
+
+		compareOutput(t, tw.Render(), `
++000000000000000000000000000000000000000000000000000000000000000+
+| Game of Thrones                                               |
++11111+11111111+11111111111+111111+11111111111111111111111111111+
+|   1 | Arya   | Stark     | 3000 |                             |
++66666+66666666+66666666666+666666+66666666666666666666666666666+
+|  20 | Jon    | Snow      | 2000 | You know nothing, Jon Snow! |
++66666+66666666+66666666666+666666+66666666666666666666666666666+
+| 300 | Tyrion | Lannister | 5000 |                             |
++77777+77777777+77777777777+777777+77777777777777777777777777777+`)
+	})
+}
+
+func TestTable_Render_Horizontal2(t *testing.T) {
+	tw := NewWriter()
+	tw.AppendHeader(testHeader)
+	tw.AppendRows(testRows)
+	tw.AppendFooter(testFooter)
+	tw.SetStyle(StyleDefault)
+	tw.SetTitle(testTitle1)
+	tw.Style().Box.Horizontal = &BoxStyleHorizontal{
+		TitleTop:     "-",
+		TitleBottom:  "-",
+		HeaderTop:    "-",
+		HeaderMiddle: "-",
+		HeaderBottom: "=",
+		RowTop:       "-",
+		RowMiddle:    "-",
+		RowBottom:    "-",
+		FooterTop:    "=",
+		FooterMiddle: "-",
+		FooterBottom: "-",
+	}
+	tw.Style().Options.DrawBorder = true
+	tw.Style().Options.SeparateColumns = true
+	tw.Style().Options.SeparateFooter = true
+	tw.Style().Options.SeparateHeader = true
+	tw.Style().Options.SeparateRows = true
+
+	compareOutput(t, tw.Render(), `
++---------------------------------------------------------------------+
+| Game of Thrones                                                     |
++-----+------------+-----------+--------+-----------------------------+
+|   # | FIRST NAME | LAST NAME | SALARY |                             |
++=====+============+===========+========+=============================+
+|   1 | Arya       | Stark     |   3000 |                             |
++-----+------------+-----------+--------+-----------------------------+
+|  20 | Jon        | Snow      |   2000 | You know nothing, Jon Snow! |
++-----+------------+-----------+--------+-----------------------------+
+| 300 | Tyrion     | Lannister |   5000 |                             |
++=====+============+===========+========+=============================+
+|     |            | TOTAL     |  10000 |                             |
++-----+------------+-----------+--------+-----------------------------+`)
+}
+
 func TestTable_Render_Colored(t *testing.T) {
 	t.Run("simple", func(t *testing.T) {
 		tw := NewWriter()
diff --git a/table/style_box_test.go b/table/style_box_test.go
new file mode 100644
index 0000000..2432fdb
--- /dev/null
+++ b/table/style_box_test.go
@@ -0,0 +1,228 @@
+package table
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestNewBoxStyleHorizontal(t *testing.T) {
+	t.Run("empty string", func(t *testing.T) {
+		horizontal := NewBoxStyleHorizontal("")
+		assert.NotNil(t, horizontal)
+		assert.Equal(t, "", horizontal.TitleTop)
+		assert.Equal(t, "", horizontal.TitleBottom)
+		assert.Equal(t, "", horizontal.HeaderTop)
+		assert.Equal(t, "", horizontal.HeaderMiddle)
+		assert.Equal(t, "", horizontal.HeaderBottom)
+		assert.Equal(t, "", horizontal.RowTop)
+		assert.Equal(t, "", horizontal.RowMiddle)
+		assert.Equal(t, "", horizontal.RowBottom)
+		assert.Equal(t, "", horizontal.FooterTop)
+		assert.Equal(t, "", horizontal.FooterMiddle)
+		assert.Equal(t, "", horizontal.FooterBottom)
+	})
+
+	t.Run("single character", func(t *testing.T) {
+		horizontal := NewBoxStyleHorizontal("-")
+		assert.NotNil(t, horizontal)
+		assert.Equal(t, "-", horizontal.TitleTop)
+		assert.Equal(t, "-", horizontal.TitleBottom)
+		assert.Equal(t, "-", horizontal.HeaderTop)
+		assert.Equal(t, "-", horizontal.HeaderMiddle)
+		assert.Equal(t, "-", horizontal.HeaderBottom)
+		assert.Equal(t, "-", horizontal.RowTop)
+		assert.Equal(t, "-", horizontal.RowMiddle)
+		assert.Equal(t, "-", horizontal.RowBottom)
+		assert.Equal(t, "-", horizontal.FooterTop)
+		assert.Equal(t, "-", horizontal.FooterMiddle)
+		assert.Equal(t, "-", horizontal.FooterBottom)
+	})
+
+	t.Run("multi-character string", func(t *testing.T) {
+		horizontal := NewBoxStyleHorizontal("═══")
+		assert.NotNil(t, horizontal)
+		assert.Equal(t, "═══", horizontal.TitleTop)
+		assert.Equal(t, "═══", horizontal.TitleBottom)
+		assert.Equal(t, "═══", horizontal.HeaderTop)
+		assert.Equal(t, "═══", horizontal.HeaderMiddle)
+		assert.Equal(t, "═══", horizontal.HeaderBottom)
+		assert.Equal(t, "═══", horizontal.RowTop)
+		assert.Equal(t, "═══", horizontal.RowMiddle)
+		assert.Equal(t, "═══", horizontal.RowBottom)
+		assert.Equal(t, "═══", horizontal.FooterTop)
+		assert.Equal(t, "═══", horizontal.FooterMiddle)
+		assert.Equal(t, "═══", horizontal.FooterBottom)
+	})
+}
+
+func TestBoxStyle_ensureHorizontalInitialized(t *testing.T) {
+	t.Run("nil horizontal initializes with MiddleHorizontal", func(t *testing.T) {
+		bs := &BoxStyle{
+			MiddleHorizontal: "-",
+			Horizontal:       nil,
+		}
+
+		bs.ensureHorizontalInitialized()
+
+		assert.NotNil(t, bs.Horizontal)
+		assert.Equal(t, "-", bs.Horizontal.TitleTop)
+		assert.Equal(t, "-", bs.Horizontal.HeaderMiddle)
+		assert.Equal(t, "-", bs.Horizontal.RowMiddle)
+	})
+
+	t.Run("nil horizontal with empty MiddleHorizontal", func(t *testing.T) {
+		bs := &BoxStyle{
+			MiddleHorizontal: "",
+			Horizontal:       nil,
+		}
+
+		bs.ensureHorizontalInitialized()
+
+		assert.NotNil(t, bs.Horizontal)
+		assert.Equal(t, "", bs.Horizontal.TitleTop)
+		assert.Equal(t, "", bs.Horizontal.HeaderMiddle)
+	})
+
+	t.Run("existing horizontal not overwritten", func(t *testing.T) {
+		existing := &BoxStyleHorizontal{
+			TitleTop:     "0",
+			TitleBottom:  "1",
+			HeaderTop:    "2",
+			HeaderMiddle: "3",
+			HeaderBottom: "4",
+			RowTop:       "5",
+			RowMiddle:    "6",
+			RowBottom:    "7",
+			FooterTop:    "8",
+			FooterMiddle: "9",
+			FooterBottom: "A",
+		}
+
+		bs := &BoxStyle{
+			MiddleHorizontal: "-",
+			Horizontal:       existing,
+		}
+
+		bs.ensureHorizontalInitialized()
+
+		// Should not be overwritten
+		assert.Equal(t, existing, bs.Horizontal)
+		assert.Equal(t, "0", bs.Horizontal.TitleTop)
+		assert.Equal(t, "1", bs.Horizontal.TitleBottom)
+		assert.Equal(t, "2", bs.Horizontal.HeaderTop)
+		assert.Equal(t, "3", bs.Horizontal.HeaderMiddle)
+		assert.Equal(t, "4", bs.Horizontal.HeaderBottom)
+		assert.Equal(t, "5", bs.Horizontal.RowTop)
+		assert.Equal(t, "6", bs.Horizontal.RowMiddle)
+		assert.Equal(t, "7", bs.Horizontal.RowBottom)
+		assert.Equal(t, "8", bs.Horizontal.FooterTop)
+		assert.Equal(t, "9", bs.Horizontal.FooterMiddle)
+		assert.Equal(t, "A", bs.Horizontal.FooterBottom)
+	})
+
+	t.Run("multiple calls are idempotent", func(t *testing.T) {
+		bs := &BoxStyle{
+			MiddleHorizontal: "-",
+			Horizontal:       nil,
+		}
+
+		bs.ensureHorizontalInitialized()
+		firstCall := bs.Horizontal
+
+		bs.ensureHorizontalInitialized()
+		secondCall := bs.Horizontal
+
+		// Should be the same instance
+		assert.Equal(t, firstCall, secondCall)
+	})
+}
+
+func TestBoxStyle_middleHorizontal(t *testing.T) {
+	t.Run("with nil horizontal initializes automatically", func(t *testing.T) {
+		bs := &BoxStyle{
+			MiddleHorizontal: "-",
+			Horizontal:       nil,
+		}
+
+		result := bs.middleHorizontal(separatorTypeRowMiddle)
+
+		assert.NotNil(t, bs.Horizontal)
+		assert.Equal(t, "-", result)
+	})
+
+	t.Run("all separator types return correct values", func(t *testing.T) {
+		bs := &BoxStyle{
+			Horizontal: &BoxStyleHorizontal{
+				TitleTop:     "0",
+				TitleBottom:  "1",
+				HeaderTop:    "2",
+				HeaderMiddle: "3",
+				HeaderBottom: "4",
+				RowTop:       "5",
+				RowMiddle:    "6",
+				RowBottom:    "7",
+				FooterTop:    "8",
+				FooterMiddle: "9",
+				FooterBottom: "A",
+			},
+		}
+
+		testCases := []struct {
+			name           string
+			separatorType  separatorType
+			expectedResult string
+		}{
+			{"TitleTop", separatorTypeTitleTop, "0"},
+			{"TitleBottom", separatorTypeTitleBottom, "1"},
+			{"HeaderTop", separatorTypeHeaderTop, "2"},
+			{"HeaderMiddle", separatorTypeHeaderMiddle, "3"},
+			{"HeaderBottom", separatorTypeHeaderBottom, "4"},
+			{"RowTop", separatorTypeRowTop, "5"},
+			{"RowMiddle", separatorTypeRowMiddle, "6"},
+			{"RowBottom", separatorTypeRowBottom, "7"},
+			{"FooterTop", separatorTypeFooterTop, "8"},
+			{"FooterMiddle", separatorTypeFooterMiddle, "9"},
+			{"FooterBottom", separatorTypeFooterBottom, "A"},
+		}
+
+		for _, tc := range testCases {
+			t.Run(tc.name, func(t *testing.T) {
+				result := bs.middleHorizontal(tc.separatorType)
+				assert.Equal(t, tc.expectedResult, result)
+			})
+		}
+	})
+
+	t.Run("default case returns RowMiddle", func(t *testing.T) {
+		bs := &BoxStyle{
+			Horizontal: &BoxStyleHorizontal{
+				RowMiddle: "default-value",
+			},
+		}
+
+		// Use an invalid separatorType that doesn't match any case
+		// separatorTypeCount is the last valid value, so anything >= separatorTypeCount
+		// should fall through to default
+		result := bs.middleHorizontal(separatorTypeCount)
+		assert.Equal(t, "default-value", result)
+
+		// Also test with a value beyond separatorTypeCount
+		result2 := bs.middleHorizontal(separatorTypeCount + 1)
+		assert.Equal(t, "default-value", result2)
+	})
+
+	t.Run("with pre-initialized horizontal", func(t *testing.T) {
+		bs := &BoxStyle{
+			MiddleHorizontal: "should-not-be-used",
+			Horizontal: &BoxStyleHorizontal{
+				TitleTop:  "custom-title",
+				RowMiddle: "custom-row",
+			},
+		}
+
+		// Should use the custom values, not MiddleHorizontal
+		assert.Equal(t, "custom-title", bs.middleHorizontal(separatorTypeTitleTop))
+		assert.Equal(t, "custom-row", bs.middleHorizontal(separatorTypeRowMiddle))
+	})
+}