# Table

*Display structured data in a clean, organized format with powerful table [widgets](../../01_Onboarding/02_Concepts/03_Widgets.md) that support sorting, filtering, and custom formatting.*

The `Table` [widget](../../01_Onboarding/02_Concepts/03_Widgets.md) is a layout container designed to render data in a tabular format. It accepts rows composed of `TableRow` elements, making it suitable for structured display of content like data listings, reports, or grids.

## Basic Usage

There is a recommended way to create tables from data arrays.
The [ToTable()](../../01_Onboarding/02_Concepts/07_ContentBuilders.md) extension method automatically converts collections into formatted tables.

```csharp
public class BasicRowTable : ViewBase
{
    public class Product
    {
        public required string Sku { get; set; }
        public required string Name { get; set; }
        public required decimal Price { get; set; }
        public required string Url { get; set; }
    }

    public override object? Build()
    {
        var products = new[] {
            new {Sku = "1234", Name = "T-shirt", Price = 10, Url = "http://example.com/tshirt"},
            new {Sku = "1235", Name = "Jeans", Price = 20, Url = "http://example.com/jeans"},
            new {Sku = "1236", Name = "Sneakers", Price = 30, Url = "http://example.com/sneakers"},
        };

        return products.ToTable()
            .Width(Size.Full());
    }
}
```

### Custom Column Builders

**Width([Size](../../04_ApiReference/IvyShared/Size.md).Full())** - sets the overall table width

**ColumnWidth(p => p.ColumnName, Size.Units())** – sets the column width with [Size](../../04_ApiReference/IvyShared/Size.md)

**ColumnWidth(p => p.ColumnName, Size.Fraction())** – sets the column width as a fraction (percentage) of available space

Long text in cells automatically gets truncated with ellipsis (...) and shows full content in tooltips on hover

**Header(p => p.ColumnName)** is used to show custom header text of the table

**Align(p => p.ColumnName, [Align](../../04_ApiReference/IvyShared/Align.md).Left|Center|Right)** - sets the alignment for both the header and data cells in the selected column. The alignment applies to the content within cells, not the entire column structure.

**Order(p => p.ColumnNameFirst, p.ColumnNameSecond, p.ColumnNameThird, ...)** - is used to order columns in a specific way

**Remove(p => p.ColumnName)** - makes possible not to show column in the table.

**Totals(p => p.ColumnName)** calculates the sum of the column if it contains numbers

**Empty(new [Card](04_Card.md)(""))** shows content when the table is empty.

```csharp
public class TableConfigurationExample : ViewBase
{
    public override object? Build()
    {
        var products = new[] {
            new {Sku = "1234", Name = "T-shirt", Price = 10, Url = "http://example.com/tshirt", _hiddenNotes = "archived"},
            new {Sku = "1235", Name = "Jeans", Price = 20, Url = "http://example.com/jeans", _hiddenNotes = "best-seller"}
        };

        return products.ToTable()
            .Width(Size.Full())
            .ColumnWidth(p => p.Price, Size.Units(100))
            .ColumnWidth(p => p.Sku, Size.Fraction(0.15f))
            .ColumnWidth(p => p.Name, Size.Fraction(0.3f))
            .ColumnWidth(p => p.Url, Size.Fraction(0.55f))
            .Header(p => p.Price, "Unit Price")
            .Header(p => p._hiddenNotes, "Internal Notes") // underscore + letter hidden automatically
            .Align(p => p.Price, Align.Right)
            .Order(p => p.Name, p => p.Price, p => p.Sku)
            .Remove(p => p.Url)
            .Totals(p => p.Price)
            .Empty(new Card("No products found").Width(Size.Full()));
    }
}
```

> **tip:** Columns whose names start with an underscore followed by a letter (for example `_hidden`, `_internalId`) are automatically removed by default. Properties that use an underscore followed by a digit or symbol (such as `_1` or `_$special`) now stay visible unless you explicitly hide them.

### Column Management Examples

The `Clear()` method hides all columns, allowing you to selectively show only the columns you need.
Use `Add()` to show specific columns in the order you want them to appear.

```csharp
public class ColumnManagementTable : ViewBase
{
    public override object? Build()
    {
        var products = new[] {
            new {Sku = "1234", Name = "T-shirt", Price = 10, Category = "Clothing", Stock = 50, _hiddenInternal = "archived"},
            new {Sku = "1235", Name = "Jeans", Price = 20, Category = "Clothing", Stock = 30, _hiddenInternal = "featured"},
            new {Sku = "1236", Name = "Sneakers", Price = 30, Category = "Footwear", Stock = 25, _hiddenInternal = "featured"}
        };

        return products.ToTable()
            .Width(Size.Full())
            .Clear()                                    // Hide all columns first
            .Add(p => p.Name)                          // Show only Name column
            .Add(p => p.Price)                         // Add Price column
            .Add(p => p.Stock)                         // Add Stock column
            .Header(p => p._hiddenInternal, "Internal Flag") // hidden by default due to underscore
            .Header(p => p.Price, "Unit Price")
            .Align(p => p.Price, Align.Right)
            .Align(p => p.Stock, Align.Center);
    }
}
```

### Advanced Aggregations

The `Totals()` method supports custom aggregation functions.
You can use LINQ methods like `Count()`, `Average()`, `Sum()`, `Max()`, and `Min()` to create sophisticated calculations for your data.

```csharp
public class AdvancedAggregationsTable : ViewBase
{
    public override object? Build()
    {
        var products = new[] {
            new {Sku = "1234", Name = "T-shirt", Price = 10, Stock = 50},
            new {Sku = "1235", Name = "Jeans", Price = 20, Stock = 30},
            new {Sku = "1236", Name = "Sneakers", Price = 30, Stock = 25}
        };

        return products.ToTable()
            .Width(Size.Full())
            .Header(p => p.Price, "Unit Price")
            .Header(p => p.Stock, "In Stock")
            .Align(p => p.Price, Align.Right)
            .Align(p => p.Stock, Align.Center)
            .Totals(p => p.Price)                      // Sum of prices
            .Totals(p => p.Stock, items => items.Count()) // Count of items
            .Totals(p => p.Price, items => items.Average(p => p.Price)) // Average price
            .Totals(p => p.Stock, items => items.Sum(p => p.Stock)); // Total stock
    }
}
```

### Pivot Tables

Pivot tables allow you to aggregate and summarize data by grouping on dimensions and calculating measures. Use the `ToPivotTable()` extension method to transform data into aggregated results that can be displayed as tables.

**Dimension** - A field to group by (e.g., Category, Region, Date)

**Measure** - An aggregated calculation (e.g., Sum, Count, Average, Max, Min)

```csharp
public class PivotTableExample : ViewBase
{
    record SalesData(string Browser, string Region, int Sessions, decimal Revenue);
    record BrowserSummary(string Browser, int TotalSessions, decimal TotalRevenue, decimal AverageRevenue);

    public override async Task<object?> Build()
    {
        var rawData = new[]
        {
            new SalesData("Chrome", "North", 150, 4500m),
            new SalesData("Chrome", "South", 120, 3600m),
            new SalesData("Firefox", "North", 80, 2400m),
            new SalesData("Firefox", "South", 60, 1800m),
            new SalesData("Safari", "North", 50, 1500m),
            new SalesData("Safari", "South", 40, 1200m)
        };

        var pivotByBrowser = await rawData.ToPivotTable()
            .Dimension("Browser", d => d.Browser)
            .Measure("Total Sessions", g => g.Sum(s => s.Sessions))
            .Measure("Total Revenue", g => g.Sum(s => s.Revenue))
            .Measure("Average Revenue", g => g.Average(s => s.Revenue))
            .ExecuteAsync();

        var pivotByBrowserAndRegion = await rawData.ToPivotTable()
            .Dimension("Browser", d => d.Browser)
            .Dimension("Region", d => d.Region)
            .Measure("Sessions", g => g.Sum(s => s.Sessions))
            .Measure("Revenue", g => g.Sum(s => s.Revenue))
            .ExecuteAsync();

        var typedResults = new List<BrowserSummary>();
        await foreach (var item in rawData.ToPivotTable()
            .Dimension("Browser", d => d.Browser)
            .Measure("TotalSessions", g => g.Sum(s => s.Sessions))
            .Measure("TotalRevenue", g => g.Sum(s => s.Revenue))
            .Measure("AverageRevenue", g => g.Average(s => s.Revenue))
            .Produces<BrowserSummary>()
            .ExecuteAsync())
        {
            typedResults.Add(item);
        }

        return Layout.Vertical().Gap(4)
            | Text.H3("Raw Data")
            | rawData.ToTable().Width(Size.Full())
            | pivotByBrowser.ToExpando().ToTable().Width(Size.Full())
            | pivotByBrowserAndRegion.ToExpando().ToTable().Width(Size.Full())
            | Text.H3("Pivot Table - Strongly Typed Result")
            | typedResults.ToTable()
                .Width(Size.Full())
                .Header(r => r.Browser, "Browser")
                .Header(r => r.TotalSessions, "Total Sessions")
                .Header(r => r.TotalRevenue, "Revenue")
                .Header(r => r.AverageRevenue, "Avg Revenue")
                .Align(r => r.TotalSessions, Align.Right)
                .Align(r => r.TotalRevenue, Align.Right)
                .Align(r => r.AverageRevenue, Align.Right);
    }
}
```

### Empty Columns Handling

The `RemoveEmptyColumns()` method automatically hides columns that contain no data (empty strings, null values, or zero values). This is useful for dynamic data where some columns might be empty across all rows.

```csharp
public class EmptyColumnsTable : ViewBase
{
    public override object? Build()
    {
        var products = new[] {
            new {Sku = "1234", Name = "T-shirt", Price = 10, Category = "Clothing", Notes = ""},
            new {Sku = "1235", Name = "Jeans", Price = 20, Category = "Clothing", Notes = ""},
            new {Sku = "1236", Name = "Sneakers", Price = 30, Category = "Footwear", Notes = ""}
        };

        return products.ToTable()
            .Width(Size.Full())
            .RemoveEmptyColumns()                      // Automatically hides "Notes" because it's empty in all rows
            .Header(p => p.Price, "Unit Price")
            .Align(p => p.Price, Align.Right);
    }
}
```

### Reset and Rebuild

The `Reset()` method restores all column settings to their default values. This is useful when you want to start fresh with a new configuration or when building dynamic table configurations.

```csharp
public class ResetTableExample : ViewBase
{
    public override object? Build()
    {
        var products = new[] {
            new {Sku = "1234", Name = "T-shirt", Price = 10, Category = "Clothing", _hiddenMetadata = "legacy"},
            new {Sku = "1235", Name = "Jeans", Price = 20, Category = "Clothing", _hiddenMetadata = "seasonal"}
        };

        return products.ToTable()
            .Width(Size.Full())
            .Remove(p => p.Category)                   // Hide Category column
            .Align(p => p.Price, Align.Right)          // Set alignment
            .Header(p => p.Price, "Unit Price")        // Custom header
            .Header(p => p._hiddenMetadata, "Metadata") // underscore + letter hidden automatically
            .Reset()                                   // Reset all settings to defaults
            .Order(p => p.Name, p => p.Price)          // Apply new order
            .Totals(p => p.Price);                     // Add totals
    }
}
```

### Manual Table

It's also possible to create manual tables with headers and other methods using rows and cells:

```csharp
public class ManualTableDemo : ViewBase
{
    public override object? Build()
    {
        return new Table(
            new TableRow(
                new TableCell("Name").IsHeader().Align(Align.Left),
                new TableCell("Age").IsHeader().Align(Align.Center),
                new TableCell("Email").IsHeader().Align(Align.Left)
            ),
            new TableRow(
                new TableCell("Alice"),
                new TableCell("30").Align(Align.Center),
                new TableCell("alice@example.com")
            )
        ).Width(Size.Full());
    }
}
```

### Builder Factory Methods

The `Builder()` method allows you to specify how different data types should be rendered. Use the builder factory methods to create appropriate renderers for your data.

```csharp
public class CellBuildersExample : ViewBase
{
    public override object? Build()
    {
        var products = new[] {
            new {Sku = "1234", Name = "T-shirt", Price = 10, Url = "http://example.com/tshirt", Description = "High quality cotton T-shirt with a comfortable fit and durable construction. Perfect for everyday wear and available in multiple colors."},
            new {Sku = "1235", Name = "Jeans", Price = 20, Url = "http://example.com/jeans", Description = "Classic denim jeans with a modern cut and premium stitching. Features include reinforced pockets, comfortable waistband, and fade-resistant fabric."}
        };

        return products.ToTable()
            .Width(Size.Full())
            .ColumnWidth(p => p.Sku, Size.Fraction(0.15f))          // 15% for SKU
            .ColumnWidth(p => p.Name, Size.Fraction(0.25f))         // 25% for Name
            .ColumnWidth(p => p.Price, Size.Fraction(0.15f))        // 15% for Price
            .ColumnWidth(p => p.Url, Size.Fraction(0.2f))           // 20% for URL
            .ColumnWidth(p => p.Description, Size.Fraction(0.25f))  // 25% for Description
            .MultiLine(p => p.Description)                    // Enable multiline for the Description column
            .Builder(p => p.Url, f => f.Link())               // Link builder
            .Builder(p => p.Description, f => f.Text())       // Text builder
            .Builder(p => p.Sku, f => f.CopyToClipboard())    // Copy to clipboard
            .Builder(p => p.Name, f => f.Default())           // Default builder
            .Header(p => p.Price, "Unit Price")
            .Align(p => p.Price, Align.Right);
    }
}
```

### Automatic Table Conversion

Any `IEnumerable` is automatically converted to a table when returned from a view. This works through the [DefaultContentBuilder](../../01_Onboarding/02_Concepts/12_ContentBuilders.md) which detects collections and converts them to tables.

```csharp
public class AutomaticTableConversion : ViewBase
{
    public override object? Build()
    {
        // Any IEnumerable is automatically converted to a table
        object data = GetProductData();
        return data; // Automatically becomes a table via DefaultContentBuilder
    }

    private object GetProductData()
    {
        return new[] {
            new {Sku = "1234", Name = "T-shirt", Price = 10},
            new {Sku = "1235", Name = "Jeans", Price = 20}
        };
    }
}
```

### Integration with Other Widgets

Tables integrate seamlessly with other Ivy widgets, allowing you to create rich, interactive [interfaces](../../01_Onboarding/02_Concepts/02_Views.md).

```csharp
public class TableIntegrationExample : ViewBase
{
    record Product(string Sku, string Name, double Price);
    public override object? Build()
    {

        var products = UseState<Product[]>(
            [new Product("1234", "T-shirt", 10.0), new Product("1235", "Jeans", 20.0)]
            );

        var client = UseService<IClientProvider>();

        var addProduct = (Event<Button> e) =>
        {
            var currentCount = products.Value.Length;

            var newProduct = new Product(
                Sku: $"SKU{1000 + currentCount}",
                Name: $"Product {currentCount + 1}",
                Price: 15.0 + currentCount
            );

            var updatedProducts = products.Value.Append(newProduct).ToArray();
            products.Set(updatedProducts);

            client.Toast($"Added {newProduct.Name}", "Product Added");
        };

        var clearProducts = (Event<Button> e) =>
        {
            products.Set(new Product[0]);
            client.Toast("All products cleared", "Products Cleared");
        };

        return Layout.Vertical()
            | new Card(
                Layout.Vertical()
                    | products.Value.ToTable().Width(Size.Full())
                    | (Layout.Horizontal().Gap(2)
                        | new Button("Add Product", addProduct).Variant(ButtonVariant.Secondary)
                        | new Button("Clear All", clearProducts).Variant(ButtonVariant.Destructive))
                    | Text.Block($"Total Products: {products.Value.Length}")
            ).Title("Product List").Width(Size.Full());
    }
}
```


## API

[View Source: Table.cs](https://github.com/Ivy-Interactive/Ivy-Framework/blob/main/src/Ivy/Widgets/Tables/Table.cs)

### Constructors

| Signature |
|-----------|
| `new Table(TableRow[] rows)` |


### Properties

| Name | Type | Setters |
|------|------|---------|
| `Height` | `Size` | - |
| `Scale` | `Scale?` | - |
| `Visible` | `bool` | - |
| `Width` | `Size` | - |