Tabulate uses a hierarchical style inheritance model that allows you to set defaults at the table level while overriding them at more specific levels (row, column, or cell).
Inheritance Hierarchy
When rendering each cell, Tabulate applies formatting in this order of precedence:
- Cell-level formatting (highest priority)
- Row-level formatting
- Column-level formatting
- Table-level formatting
- Default formatting (lowest priority)
More specific formatting always overrides more general formatting. This allows you to set broad defaults and selectively override them where needed.
How It Works
Table table;
table.add_row({"A1", "A2", "A3"});
table.add_row({"B1", "B2", "B3"});
table.add_row({"C1", "C2", "C3"});
// Table-level: all cells will be bold
table.format().font_style({FontStyle::bold});
// Row-level: all cells in row 1 will be yellow (and still bold from table)
table[1].format().font_color(Color::yellow);
// Cell-level: this specific cell will be red (overrides yellow, still bold)
table[1][1].format().font_color(Color::red);
Result:
- Cell
[0][0], [0][1], [0][2]: Bold (from table)
- Cell
[1][0]: Bold + Yellow (from table + row)
- Cell
[1][1]: Bold + Red (from table + cell, cell overrides row)
- Cell
[1][2]: Bold + Yellow (from table + row)
- Cell
[2][0], [2][1], [2][2]: Bold (from table)
When no formatting is specified, Tabulate uses these defaults:
// Font
font_align = FontAlign::left
font_style = {} (empty, no styles)
font_color = Color::none
font_background_color = Color::none
// Padding
padding_left = 1
padding_right = 1
padding_top = 0
padding_bottom = 0
// Borders
border_top = "-"
border_bottom = "-"
border_left = "|"
border_right = "|"
show_border_left = true
show_border_right = true
show_border_top = true
show_border_bottom = true
// Corners
corner_top_left = "+"
corner_top_right = "+"
corner_bottom_left = "+"
corner_bottom_right = "+"
// Column separator
column_separator = "|"
// Internationalization
multi_byte_characters = false
locale = ""
trim_mode = TrimMode::kBoth
// Other
show_row_separator = false
Inheritance in Action
Example 1: Table-Wide Styling with Cell Override
Table table;
table.add_row({"Normal", "Normal", "Special"});
table.add_row({"Normal", "Normal", "Normal"});
// Apply blue background to entire table
table.format().font_background_color(Color::blue);
// Override specific cell with red background
table[0][2].format().font_background_color(Color::red);
Result:
- Most cells: Blue background
- Cell
[0][2]: Red background (overrides table-level blue)
Table universal_constants;
universal_constants.add_row({"Quantity", "Value"});
universal_constants.add_row({"Speed of light", "299 792 458 m·s⁻¹"});
// Format entire header row
universal_constants[0].format()
.font_background_color(Color::red)
.font_align(FontAlign::center)
.font_style({FontStyle::underline});
// Highlight one specific cell in header
universal_constants[0][1].format()
.font_background_color(Color::blue)
.font_color(Color::white);
Result:
- Cell
[0][0]: Red background, centered, underlined
- Cell
[0][1]: Blue background (overrides red), white text, centered (inherited), underlined (inherited)
Notice how the cell-level formatting overrides font_background_color and font_color but inherits font_align and font_style from the row level.
Table movies;
movies.add_row({"S/N", "Movie Name", "Director", "Budget"});
movies.add_row({"1", "Toy Story 4", "Josh Cooley", "$200M"});
movies.add_row({"2", "Sully", "Clint Eastwood", "$60M"});
// Right-align the budget column
movies.column(3).format().font_align(FontAlign::right);
// Center-align the header row (overrides column alignment for header)
movies[0].format().font_align(FontAlign::center);
Result:
- Header row (row 0): All cells centered
- Budget column (column 3), rows 1-2: Right-aligned
- Other cells: Left-aligned (default)
Tabulate implements style inheritance through format merging. When rendering a cell:
// Pseudocode for how Tabulate resolves cell formatting
Format resolved_format = Format::merge(
Format::merge(
Format::merge(cell_format, row_format),
column_format
),
table_format
);
The Format::merge(first, second) function:
- Takes two formats
- Returns a new format with properties from both
- First format has higher precedence - its values override second’s values
- If a property is not set in first, it uses second’s value
Font styles are merged using set union, so multiple style sources can contribute styles that are all applied together.
Practical Patterns
Pattern 1: Styled Header with Default Body
Table table;
table.add_row({"Name", "Age", "City"});
table.add_row({"Alice", "30", "NYC"});
table.add_row({"Bob", "25", "LA"});
// Style only the header
table[0].format()
.font_style({FontStyle::bold})
.font_color(Color::yellow)
.font_align(FontAlign::center);
// Body rows use defaults (left-aligned, no color, no bold)
Pattern 2: Alternating Row Colors
Table table;
table.add_row({"Col1", "Col2", "Col3"});
table.add_row({"A1", "A2", "A3"});
table.add_row({"B1", "B2", "B3"});
table.add_row({"C1", "C2", "C3"});
// Style rows with alternating colors
for (size_t i = 1; i < table.size(); ++i) {
if (i % 2 == 0) {
table[i].format().font_background_color(Color::blue);
}
}
Pattern 3: Column Types with Special Cells
Table table;
table.add_row({"ID", "Name", "Amount"});
table.add_row({"1", "Item A", "100.00"});
table.add_row({"2", "Item B", "250.50"});
table.add_row({"TOTAL", "", "350.50"});
// Right-align the amount column
table.column(2).format().font_align(FontAlign::right);
// Bold the total row
table[3].format().font_style({FontStyle::bold});
// The total row's amount cell is both right-aligned (from column)
// and bold (from row) - inheritance works together!
Pattern 4: Global Style with Exceptions
Table table;
table.add_row({"Normal", "Normal", "Normal"});
table.add_row({"Normal", "ERROR", "Normal"});
table.add_row({"Normal", "Normal", "Normal"});
// Set table-wide padding
table.format().padding(2);
// Highlight error cell
table[1][1].format()
.font_color(Color::red)
.font_style({FontStyle::bold});
// Padding of 2 is still inherited from table level
When both column and row formatting apply to a cell, row formatting takes precedence because it’s more specific in the hierarchy.
Table table;
table.add_row({"A", "B"});
// Column wants blue
table.column(0).format().font_color(Color::blue);
// Row wants red
table[0].format().font_color(Color::red);
// Cell [0][0] will be RED (row wins over column)
The precedence order is:
- Cell (highest)
- Row
- Column
- Table
- Default (lowest)
This precedence model allows row-level overrides to affect entire rows uniformly, while column formatting provides column-wide defaults.
Best Practices
1. Set Defaults at the Table Level
// Good: set common properties once
table.format()
.padding_left(2)
.padding_right(2)
.font_style({FontStyle::bold});
// Good: format header row as a unit
table[0].format()
.font_background_color(Color::blue)
.font_color(Color::white)
.font_align(FontAlign::center);
// Good: format columns by data type
table.column(0).format().font_align(FontAlign::left); // IDs
table.column(1).format().font_align(FontAlign::left); // Names
table.column(2).format().font_align(FontAlign::right); // Numbers
table.column(3).format().font_align(FontAlign::center); // Status
// Good: highlight specific important cells
table[5][2].format()
.font_color(Color::red) // Error value
.font_style({FontStyle::bold});
// Avoid: formatting every cell individually (use row/column/table instead)
Think of inheritance as “defaults that can be overridden.” Set broad defaults at high levels and use lower levels only when you need to diverge from those defaults.
Complete Example
#include <tabulate/table.hpp>
using namespace tabulate;
int main() {
Table table;
table.add_row({"Company", "Contact", "Country"});
table.add_row({"Alfreds Futterkiste", "Maria Anders", "Germany"});
table.add_row({"Centro comercial Moctezuma", "Francisco Chang", "Mexico"});
table.add_row({"Ernst Handel", "Roland Mendel", "Austria"});
// Table level: set column widths
table.column(0).format().width(40);
table.column(1).format().width(30);
table.column(2).format().width(30);
// Row level: format header
for (auto& cell : table[0]) {
cell.format()
.font_style({FontStyle::underline})
.font_align(FontAlign::center);
}
// Column level: right-align first column (except header)
for (auto& cell : table.column(0)) {
if (cell.get_text() != "Company") {
cell.format().font_align(FontAlign::right);
}
}
// Row level: alternate row colors
size_t index = 0;
for (auto& row : table) {
if (index > 0 && index % 2 == 0) {
for (auto& cell : row) {
cell.format().font_background_color(Color::blue);
}
}
index += 1;
}
std::cout << table << std::endl;
}
This example demonstrates inheritance at every level working together to create a well-formatted table.