Files
Htmx/docs/Components/Card.md
T
2026-05-05 23:55:26 +05:00

171 lines
5.0 KiB
Markdown

# Card
A bordered box for grouping related content — like a physical card you might hold in your hand. It has three distinct zones: a header (title + subtitle), a body (your content), and a footer (usually actions).
---
## Quick example
```csharp
new Card(
content: "<p>Your subscription renews on July 1.</p>",
title: "Billing",
description: "Current plan: Pro")
```
---
## All the options
```csharp
public Card(
string content,
string title = "",
string description = "",
string footer = "",
string extraClasses = "")
```
| Parameter | What it does |
|---|---|
| `content` | The body of the card — always shown. Raw HTML. |
| `title` | Optional bold heading at the top of the card. |
| `description` | Optional smaller subtitle below the title. |
| `footer` | Optional section at the bottom, typically holding action buttons. Raw HTML. |
| `extraClasses` | Additional Tailwind classes on the outer `div` — useful for `max-w-sm`, `col-span-2`, etc. |
The header section (title + description) is omitted entirely when both are empty. Same for the footer.
---
## Real-world examples
### A stats card on a dashboard
```csharp
new Card(
title: "Total Users",
description: "All registered accounts",
content: $"<p class=\"text-4xl font-bold\">{userCount:N0}</p>")
```
### A confirmation card with footer buttons
Buttons and other components need to be pre-rendered to HTML strings when used inside `content` or `footer`:
```csharp
string ToHtml(IHtmxComponent c)
{
var w = new System.Buffers.ArrayBufferWriter<byte>();
c.Render(new HtmxRenderContext(w));
return System.Text.Encoding.UTF8.GetString(w.WrittenSpan);
}
new Card(
title: "Delete account",
description: "This action cannot be undone.",
content: "<p>All your data will be permanently removed.</p>",
footer: ToHtml(new Button("Cancel", variant: "outline"))
+ ToHtml(new Button("Delete", variant: "destructive", type: "submit")))
```
### A grid of cards
Cards are most commonly placed in a CSS grid in the page template:
```html
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
$$Card1$$
$$Card2$$
$$Card3$$
</div>
```
### Constrained width (e.g. a login card)
```csharp
new Card(
content: "...login form HTML...",
title: "Welcome back",
description: "Sign in to your account",
extraClasses: "max-w-sm mx-auto")
```
---
## How it works
Card uses CSS variables (`bg-card`, `text-card-foreground`, `border-border`) which automatically adapt to dark mode. The header and footer sections are skipped entirely in the rendered HTML when they are not needed — they do not leave empty divs behind.
All strings passed to `content` and `footer` are raw HTML. HTML-encode any user-supplied values before passing them in.
---
## Complete page example
**`Templates/DashboardPage.htmx`**
```html
<div class="max-w-5xl mx-auto py-10">
<h1 class="text-2xl font-bold mb-8">Dashboard</h1>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
$$UsersCard$$
$$RevenueCard$$
$$OrdersCard$$
</div>
</div>
```
**`Templates/DashboardPage.htmx.cs`**
```csharp
namespace Htmx.ApiDemo.Templates;
public sealed class DashboardPage : DashboardPageBase
{
private readonly IHtmxComponent _users;
private readonly IHtmxComponent _revenue;
private readonly IHtmxComponent _orders;
public DashboardPage(DashboardStats stats)
{
_users = new Components.Card(
title: "Total users",
description: "Registered accounts",
content: $"<p class=\"text-4xl font-bold\">{stats.UserCount:N0}</p>");
_revenue = new Components.Card(
title: "Revenue",
description: "This month",
content: $"<p class=\"text-4xl font-bold\">${stats.MonthlyRevenue:N2}</p>");
_orders = new Components.Card(
title: "Open orders",
description: "Awaiting fulfillment",
content: $"<p class=\"text-4xl font-bold\">{stats.OpenOrders}</p>",
footer: """<a href="/orders" class="text-sm text-primary underline">View all</a>""");
}
protected override void RenderUsersCard(HtmxRenderContext ctx) => _users.Render(ctx.Next());
protected override void RenderRevenueCard(HtmxRenderContext ctx) => _revenue.Render(ctx.Next());
protected override void RenderOrdersCard(HtmxRenderContext ctx) => _orders.Render(ctx.Next());
}
```
**GET handler**
```csharp
[Handler]
[MapGet("/dashboard")]
public static partial class GetDashboardHandler
{
public record Query();
private static Task<IResult> HandleAsync(
Query _, HttpContext ctx, CancellationToken ct)
{
var stats = new DashboardStats(UserCount: 1_204, MonthlyRevenue: 48_320.50m, OpenOrders: 37);
return ctx.WriteHtmxPage(new DashboardPage(stats), title: "Dashboard");
}
}
public record DashboardStats(int UserCount, decimal MonthlyRevenue, int OpenOrders);
```