# Table
A styled HTML table with a header row, data rows, and optional caption and footer. Use it when you have a list of items with multiple columns — user lists, order history, product inventories.
---
## Quick example
```csharp
new Table(
headers: new[] { "Name", "Email", "Role" },
rows: users.Select(u => new[]
{
System.Web.HttpUtility.HtmlEncode(u.DisplayName ?? ""),
System.Web.HttpUtility.HtmlEncode(u.Email),
u.Role
}))
```
---
## All the options
```csharp
public Table(
IEnumerable headers,
IEnumerable> rows,
string caption = "",
string footer = "")
```
| Parameter | What it does |
|---|---|
| `headers` | Column heading strings. |
| `rows` | Each inner collection is one table row. Each string in it is a cell. Cells are raw HTML. |
| `caption` | Optional summary text displayed below the table. |
| `footer` | Optional footer text that spans all columns. |
> **HTML safety:** Cell values are inserted as raw HTML. Always use `System.Web.HttpUtility.HtmlEncode()` on any user-supplied strings before passing them in.
---
## Real-world examples
### Table with a count caption and footer note
```csharp
new Table(
headers: new[] { "Product", "Price", "Stock" },
rows: products.Select(p => new[]
{
System.Web.HttpUtility.HtmlEncode(p.Name),
$"${p.Price:F2}",
p.Stock.ToString()
}),
caption: $"Showing {products.Count} products",
footer: "Prices include VAT")
```
### Status column with a Badge
Pre-render the badge to an HTML string and embed it in the cell:
```csharp
string RenderBadge(string label, string variant = "default")
{
var buf = new System.Buffers.ArrayBufferWriter();
new Badge(label, variant).Render(new HtmxRenderContext(buf));
return System.Text.Encoding.UTF8.GetString(buf.WrittenSpan);
}
new Table(
headers: new[] { "Name", "Status" },
rows: users.Select(u => new[]
{
System.Web.HttpUtility.HtmlEncode(u.DisplayName ?? ""),
u.IsActive ? RenderBadge("Active", "default") : RenderBadge("Inactive", "secondary")
}))
```
### Row actions with HTMX edit button
```csharp
string EditLink(string id) =>
$"""""";
new Table(
headers: new[] { "Name", "" },
rows: users.Select(u => new[]
{
System.Web.HttpUtility.HtmlEncode(u.DisplayName ?? ""),
EditLink(u.Id!)
}))
```
---
## How it works
Table wraps a standard `
` in an `overflow-auto` container so it scrolls horizontally on small screens. Header cells use `
` and data cells use `
`. The `caption` is rendered inside a `
` element below the table; the `footer` spans all columns in a `
` row.
```
---
## Tips and tricks
- Cells are raw HTML — HTML-encode any user-supplied string values before inserting them to prevent XSS.
- Use `System.Web.HttpUtility.HtmlEncode(value)` or `System.Net.WebUtility.HtmlEncode(value)` for any untrusted data.
- Pair with `Pagination` below the table for large datasets.
- For sortable columns, replace header strings with anchor tags containing HTMX sort-request attributes.
- The `footer` string spans all columns — use it for totals, notes, or a "load more" link.
- The `footer` string spans all columns — use it for totals, notes, or a "load more" link.
---
## Complete page example
**`Templates/AdminUsersPage.htmx`**
```html
Users
$$InviteBtn$$
$$UsersTable$$
```
**`Templates/AdminUsersPage.htmx.cs`**
```csharp
namespace Htmx.ApiDemo.Templates;
public sealed class AdminUsersPage : AdminUsersPageBase
{
private readonly IHtmxComponent _table;
private readonly IHtmxComponent _invite;
public AdminUsersPage(IEnumerable users, int total)
{
_invite = new Components.Button(
"Invite user",
variant: "default",
extraAttributes: """hx-get="/admin/users/invite" hx-target="#modal" hx-swap="innerHTML" """);
var rows = users.Select(u => new[]
{
System.Net.WebUtility.HtmlEncode(u.DisplayName ?? ""),
System.Net.WebUtility.HtmlEncode(u.Email),
u.CreatedAt.ToString("yyyy-MM-dd"),
$"""""",
});
_table = new Components.Table(
caption: "All registered accounts",
headers: new[] { "Name", "Email", "Joined", "Actions" },
rows: rows,
footer: $"{total} users total");
}
protected override void RenderInviteBtn(HtmxRenderContext ctx) => _invite.Render(ctx.Next());
protected override void RenderUsersTable(HtmxRenderContext ctx) => _table.Render(ctx.Next());
}
```
**GET handler**
```csharp
[Handler]
[MapGet("/admin/users")]
public static partial class GetAdminUsersHandler
{
public record Query();
private static async Task HandleAsync(
Query _, HttpContext ctx, MongoDbService db, CancellationToken ct)
{
var users = await db.GetAllUsersAsync(ct);
var list = users.ToList();
return await ctx.WriteHtmxPage(
new AdminUsersPage(list, list.Count), title: "Admin – Users");
}
}
```