# Table A styled HTML data table with a header row, optional caption, optional footer row, and one or more data rows. All data is plain strings. --- ## HTML structure ``` div.overflow-auto.rounded-md.border.border-border table.w-full.text-sm.caption-bottom caption.mt-4.text-sm.text-muted-foreground ← omitted when empty {caption} thead tr.border-b.bg-muted/50 th.h-12.px-4.text-left.font-medium ← one per header {header} tbody tr.border-b.hover:bg-muted/40 ← one per row; last row has no border td.p-4 ← one per cell; raw HTML {cell} tfoot ← omitted when empty tr td[colspan=N].p-4.text-muted-foreground {footer} ``` --- ## CSS mechanics | Class | Effect | |---|---| | `overflow-auto` on wrapper | Horizontal scroll on small screens | | `bg-muted/50` on header | Slightly tinted header row | | `hover:bg-muted/40` on data rows | Subtle hover highlight | | `border-b` on rows | Row separator lines | | `caption-bottom` | Caption appears below the table | --- ## Constructor signature ```csharp public Table( IEnumerable headers, IEnumerable> rows, string caption = "", string footer = "") ``` | Parameter | Description | |---|---| | `headers` | Column heading strings | | `rows` | Each inner `IEnumerable` is one row; cells are raw HTML | | `caption` | Optional caption below the table | | `footer` | Optional footer cell (spans all columns) | --- ## Usage examples ### Basic data table ```csharp new Table( headers: new[] { "Name", "Email", "Role" }, rows: users.Select(u => new[] { u.DisplayName ?? "", u.Email, u.Role })) ``` ### With caption and footer ```csharp new Table( headers: new[] { "Product", "Price", "Stock" }, rows: products.Select(p => new[] { p.Name, $"${p.Price:F2}", p.Stock.ToString() }), caption: $"Showing {products.Count} products", footer: "Prices include VAT") ``` ### Cells with HTML content (e.g. badges) ```csharp // Pre-render a Badge to HTML string string ActiveBadge() { var buf = new System.Buffers.ArrayBufferWriter(); new Badge("Active").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 ? ActiveBadge() : "" })) ``` ### With action buttons per row ```csharp string EditBtn(string id) => $""" """; new Table( headers: new[] { "Name", "Actions" }, rows: users.Select(u => new[] { System.Web.HttpUtility.HtmlEncode(u.DisplayName ?? ""), EditBtn(u.Id!) })) ``` --- ## 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"); } } ```