Tables
Build data tables with fixed and proportional columns, spanning cells, repeating headers, alternating row colors, and automatic multi-page pagination.
Basic Table
A table is defined inside a .Table() block. First, declare columns with ColumnsDefinition. Then add cells with table.Cell() — cells fill left-to-right, top-to-bottom, wrapping to the next row when the column count is reached.
col.Item().Table(table =>
{
table.ColumnsDefinition(c =>
{
c.ConstantColumn(60); // fixed 60pt
c.RelativeColumn(3); // 3/4 of remaining
c.RelativeColumn(1); // 1/4 of remaining
});
// Header row
table.Cell().Background(Color.FromHex("#333")).Padding(5)
.Text("#").FontColor(Color.White).Bold();
table.Cell().Background(Color.FromHex("#333")).Padding(5)
.Text("Description").FontColor(Color.White).Bold();
table.Cell().Background(Color.FromHex("#333")).Padding(5)
.Text("Amount").FontColor(Color.White).Bold();
// Data rows
table.Cell().Padding(5).Text("1");
table.Cell().Padding(5).Text("Web development services");
table.Cell().Padding(5).AlignRight().Text("$4,500.00");
table.Cell().Padding(5).Text("2");
table.Cell().Padding(5).Text("Hosting and infrastructure");
table.Cell().Padding(5).AlignRight().Text("$1,200.00");
table.Cell().Padding(5).Text("3");
table.Cell().Padding(5).Text("Annual support contract");
table.Cell().Padding(5).AlignRight().Text("$2,400.00");
});
Column Definitions
Two column types are available. They can be mixed freely — constant columns are reserved first, then remaining space is divided among relative columns.
| Method | Description |
|---|---|
c.ConstantColumn(float, Unit) | Fixed width in points (or specified unit) |
c.RelativeColumn(float) | Proportional share of remaining space (default weight: 1) |
table.ColumnsDefinition(c =>
{
c.ConstantColumn(40); // 40pt for row numbers
c.RelativeColumn(2); // 2x weight for name
c.RelativeColumn(1); // 1x weight for category
c.ConstantColumn(80); // 80pt for price
});
Tip: For a five-column table with equal widths, call c.RelativeColumn() five times with the default weight of 1. For a table that mimics a fixed layout, use all ConstantColumn definitions.
Row and Column Spanning
A cell can span multiple columns or rows using .ColumnSpan() and .RowSpan(). Cells fill in reading order and skip positions occupied by spanning cells.
col.Item().Table(table =>
{
table.ColumnsDefinition(c =>
{
c.RelativeColumn();
c.RelativeColumn();
c.RelativeColumn();
});
// Row 1: full-width header spanning all 3 columns
table.Cell().ColumnSpan(3)
.Background(Color.FromHex("#1A73E8")).Padding(8)
.Text("Performance Summary").FontColor(Color.White).Bold().FontSize(14);
// Row 2
table.Cell().RowSpan(2).Background(Color.FromHex("#E8F0FE")).Padding(8)
.Text("Category A\n(spans 2 rows)");
table.Cell().Padding(8).Text("Metric 1: 95%");
table.Cell().Padding(8).Text("Metric 2: 88%");
// Row 3 (first column skipped by RowSpan above)
table.Cell().Padding(8).Text("Metric 3: 92%");
table.Cell().Padding(8).Text("Metric 4: 97%");
});
| Method | Description |
|---|---|
.ColumnSpan(uint) | Number of columns the cell occupies (default: 1) |
.RowSpan(uint) | Number of rows the cell occupies (default: 1) |
.Column(uint) | Explicitly set the column position (1-based) |
.Row(uint) | Explicitly set the row position (1-based) |
Cell Styling
Every table.Cell() returns an IContainer, so the full range of styling is available: background, border, padding, alignment, corner radius, and more.
table.Cell()
.Background(Color.FromHex("#F5F5F5"))
.Border(0.5f, Color.FromHex("#DDD"))
.Padding(8)
.AlignCenter()
.AlignMiddle()
.Text("Styled cell");
Individual Border Sides
table.Cell()
.BorderBottom(1, Color.FromHex("#E0E0E0"))
.Padding(6)
.Text("Bottom border only");
Alternating Row Colors
Loop over your data and apply different backgrounds on even/odd rows.
var items = new[]
{
("Widget A", 10, 29.99m),
("Widget B", 5, 49.99m),
("Widget C", 20, 9.99m),
("Widget D", 3, 199.99m),
("Widget E", 15, 14.99m),
};
col.Item().Table(table =>
{
table.ColumnsDefinition(c =>
{
c.RelativeColumn(3);
c.ConstantColumn(60);
c.ConstantColumn(80);
});
// Header
table.Cell().Background(Color.FromHex("#1A73E8")).Padding(6)
.Text("Product").FontColor(Color.White).Bold();
table.Cell().Background(Color.FromHex("#1A73E8")).Padding(6)
.Text("Qty").FontColor(Color.White).Bold();
table.Cell().Background(Color.FromHex("#1A73E8")).Padding(6)
.Text("Price").FontColor(Color.White).Bold();
for (int i = 0; i < items.Length; i++)
{
var bg = i % 2 == 0
? Color.White
: Color.FromHex("#F5F5F5");
var (name, qty, price) = items[i];
table.Cell().Background(bg).Padding(6).Text(name);
table.Cell().Background(bg).Padding(6).AlignRight().Text(qty);
table.Cell().Background(bg).Padding(6).AlignRight().Text($"${price:N2}");
}
});
Repeating Table Header
Use table.Header() to define cells that repeat at the top of the table on every page when the table spans multiple pages.
col.Item().Table(table =>
{
table.ColumnsDefinition(c =>
{
c.ConstantColumn(40);
c.RelativeColumn();
c.ConstantColumn(80);
});
// Repeating header
table.Header(header =>
{
header.Cell().Background(Color.FromHex("#333")).Padding(6)
.Text("#").FontColor(Color.White).Bold();
header.Cell().Background(Color.FromHex("#333")).Padding(6)
.Text("Item").FontColor(Color.White).Bold();
header.Cell().Background(Color.FromHex("#333")).Padding(6)
.Text("Total").FontColor(Color.White).Bold();
});
// Body rows (these paginate, header repeats)
for (int i = 1; i <= 200; i++)
{
table.Cell().BorderBottom(0.5f, Color.FromHex("#E0E0E0")).Padding(4)
.Text(i.ToString());
table.Cell().BorderBottom(0.5f, Color.FromHex("#E0E0E0")).Padding(4)
.Text($"Line item {i}");
table.Cell().BorderBottom(0.5f, Color.FromHex("#E0E0E0")).Padding(4)
.AlignRight().Text($"${i * 12.50m:N2}");
}
});
Repeating Table Footer
Use table.Footer() to define cells that repeat at the bottom of the table on every page.
table.Footer(footer =>
{
footer.Cell().ColumnSpan(2)
.BorderTop(1, Color.FromHex("#333")).PaddingTop(6)
.Text("Subtotal").Bold();
footer.Cell()
.BorderTop(1, Color.FromHex("#333")).PaddingTop(6)
.AlignRight().Text("$2,500.00").Bold();
});
ExtendLastCellsToTableBottom
When enabled, the last cell in each column stretches vertically to fill the remaining table height. Useful for maintaining a uniform table bottom edge.
table.ExtendLastCellsToTableBottom();
Complex Example: Invoice Line Items
var lineItems = new[]
{
("Consulting services - Q1", "40 hours at $150/hr", 6000.00m),
("Software license - Enterprise", "Annual subscription", 12000.00m),
("Training workshop", "2-day on-site training", 4500.00m),
("Support contract", "12 months premium support", 3600.00m),
("Cloud infrastructure", "Monthly hosting, Apr 2026", 850.00m),
};
col.Item().Table(table =>
{
table.ColumnsDefinition(c =>
{
c.RelativeColumn(3); // Description
c.RelativeColumn(2); // Details
c.ConstantColumn(100); // Amount
});
// Repeating header
table.Header(h =>
{
var headerBg = Color.FromHex("#1A237E");
h.Cell().Background(headerBg).Padding(8)
.Text("Description").FontColor(Color.White).Bold();
h.Cell().Background(headerBg).Padding(8)
.Text("Details").FontColor(Color.White).Bold();
h.Cell().Background(headerBg).Padding(8)
.Text("Amount").FontColor(Color.White).Bold();
});
// Data rows
decimal total = 0;
for (int i = 0; i < lineItems.Length; i++)
{
var (desc, details, amount) = lineItems[i];
total += amount;
var bg = i % 2 == 0 ? Color.White : Color.FromHex("#F5F5F5");
var borderColor = Color.FromHex("#E0E0E0");
table.Cell().Background(bg).BorderBottom(0.5f, borderColor)
.Padding(8).Text(desc);
table.Cell().Background(bg).BorderBottom(0.5f, borderColor)
.Padding(8).Text(details).FontColor(Color.FromHex("#666"));
table.Cell().Background(bg).BorderBottom(0.5f, borderColor)
.Padding(8).AlignRight().Text($"${amount:N2}");
}
// Totals row
table.Cell().ColumnSpan(2)
.BorderTop(2, Color.FromHex("#1A237E")).PaddingTop(8).PaddingRight(8)
.AlignRight().Text("Total:").Bold().FontSize(13);
table.Cell()
.BorderTop(2, Color.FromHex("#1A237E")).PaddingTop(8)
.AlignRight().Text($"${total:N2}").Bold().FontSize(13);
});
Complex Example: Data Grid
col.Item().Table(table =>
{
table.ColumnsDefinition(c =>
{
c.ConstantColumn(30); // Row number
c.RelativeColumn(); // Name
c.ConstantColumn(80); // Department
c.ConstantColumn(60); // Score
c.ConstantColumn(70); // Status
});
table.Header(h =>
{
var hdr = Color.FromHex("#37474F");
string[] headers = { "#", "Employee", "Dept", "Score", "Status" };
foreach (var text in headers)
h.Cell().Background(hdr).Padding(5)
.Text(text).FontColor(Color.White).Bold().FontSize(9);
});
var employees = new[]
{
("Alice Chen", "Engineering", 94, "Active"),
("Bob Martinez", "Marketing", 87, "Active"),
("Carol Okafor", "Engineering", 91, "On Leave"),
("Dave Kim", "Sales", 78, "Active"),
("Eve Johansson", "Engineering", 96, "Active"),
};
for (int i = 0; i < employees.Length; i++)
{
var (name, dept, score, status) = employees[i];
var bg = i % 2 == 0 ? Color.White : Color.FromHex("#FAFAFA");
var border = Color.FromHex("#EEEEEE");
table.Cell().Background(bg).BorderBottom(0.5f, border).Padding(5)
.Text((i + 1).ToString()).FontSize(9).FontColor(Color.Grey);
table.Cell().Background(bg).BorderBottom(0.5f, border).Padding(5)
.Text(name).FontSize(9);
table.Cell().Background(bg).BorderBottom(0.5f, border).Padding(5)
.Text(dept).FontSize(9);
table.Cell().Background(bg).BorderBottom(0.5f, border).Padding(5)
.AlignCenter().Text(score.ToString()).FontSize(9)
.FontColor(score >= 90 ? Color.FromHex("#2E7D32") : Color.FromHex("#333"));
table.Cell().Background(bg).BorderBottom(0.5f, border).Padding(5)
.Text(status).FontSize(9)
.FontColor(status == "Active" ? Color.FromHex("#1B5E20") : Color.FromHex("#E65100"));
}
});
Explicit Cell Positioning
For irregular layouts, bypass the automatic left-to-right fill order by setting explicit row and column positions.
table.Cell().Row(1).Column(1).Text("A1");
table.Cell().Row(1).Column(3).Text("A3"); // skip column 2
table.Cell().Row(2).Column(2).Text("B2"); // different row, different column
table.Cell().Row(1).Column(2).RowSpan(2).Background(Color.Yellow).Text("Spans A2:B2");
Note: Row and column positions are 1-based. When using explicit positioning, the automatic fill cursor is not advanced — you are responsible for ensuring cells do not overlap. Overlapping cells produce undefined layout behavior.