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.

MethodDescription
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%");
});
MethodDescription
.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.