b530bb8c97
Co-authored-by: Copilot <copilot@github.com>
5.3 KiB
5.3 KiB
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
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
public Table(
IEnumerable<string> headers,
IEnumerable<IEnumerable<string>> 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
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:
string RenderBadge(string label, string variant = "default")
{
var buf = new System.Buffers.ArrayBufferWriter<byte>();
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
string EditLink(string id) =>
$"""<button hx-get="/users/{id}/edit" hx-target="#modal" class="text-sm underline">Edit</button>""";
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 <table> in an overflow-auto container so it scrolls horizontally on small screens. Header cells use <th> and data cells use <td>. The caption is rendered inside a <caption> element below the table; the footer spans all columns in a <tfoot> 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
<div class="max-w-5xl mx-auto py-10">
<div class="flex items-center justify-between mb-6">
<h1 class="text-2xl font-bold">Users</h1>
$$InviteBtn$$
</div>
$$UsersTable$$
</div>
Templates/AdminUsersPage.htmx.cs
namespace Htmx.ApiDemo.Templates;
public sealed class AdminUsersPage : AdminUsersPageBase
{
private readonly IHtmxComponent _table;
private readonly IHtmxComponent _invite;
public AdminUsersPage(IEnumerable<ApplicationUser> 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"),
$"""<button class="text-destructive text-xs" hx-delete="/admin/users/{u.Id}" hx-confirm="Delete this user?">Delete</button>""",
});
_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
[Handler]
[MapGet("/admin/users")]
public static partial class GetAdminUsersHandler
{
public record Query();
private static async Task<IResult> 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");
}
}