Files
Htmx/docs/Components/Skeleton.md
T
2026-05-04 19:57:48 +05:00

186 lines
4.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Skeleton
An animated loading placeholder. Use it in place of real content while data is being fetched or rendered asynchronously. The animation communicates to the user that content is loading.
---
## HTML structure
```
div.animate-pulse.rounded-md.bg-muted.{classes}
```
---
## CSS mechanics
| Class | Effect |
|---|---|
| `animate-pulse` | Tailwind's built-in fade-in/out animation (1.5s loop) |
| `bg-muted` | Neutral muted background color from the theme |
| `rounded-md` | Slightly rounded corners |
| User-supplied `classes` | Control size and shape (e.g. `h-4 w-32`, `h-10 w-full`, `rounded-full h-12 w-12`) |
---
## Constructor signature
```csharp
public Skeleton(string classes = "")
```
| Parameter | Description |
|---|---|
| `classes` | Tailwind classes controlling size, shape, and spacing |
---
## Usage examples
### Text line placeholders
```csharp
new Skeleton("h-4 w-3/4 mb-2")
new Skeleton("h-4 w-1/2")
```
### Avatar placeholder
```csharp
new Skeleton("rounded-full h-12 w-12")
```
### Card skeleton loader
```csharp
new Card(
content: """
<div class="flex items-center gap-4">
<!-- Render each Skeleton eagerly to a string or use slot injection -->
</div>
<div class="mt-4 space-y-2">
</div>
""")
```
### Full-width block placeholder
```csharp
new Skeleton("h-10 w-full")
```
### HTMX skeleton swap pattern
```html
<!-- Shown immediately; HTMX replaces with real content -->
<div id="user-list"
hx-get="/users"
hx-trigger="load"
hx-swap="outerHTML">
$$UserListSkeleton$$
</div>
```
The page renders the skeleton on initial load; the HTMX request fires immediately and replaces it once the data arrives.
---
## Tips and tricks
- Multiple `Skeleton` elements stacked in a `div.space-y-2` create a convincing text-block placeholder.
- `rounded-full` makes a circle — useful for avatar skeletons. Combine with equal `h-*` and `w-*` values.
- The `classes` parameter replaces the default empty string — provide complete size + spacing classes.
- For table skeletons, render a `Table` with each cell containing a Skeleton HTML string (pre-rendered to a string via `ArrayBufferWriter<byte>`).
- Do not use Skeleton for truly empty states (no data to show) — use an `Alert` or empty-state illustration instead.
- Do not use Skeleton for truly empty states (no data to show) — use an `Alert` or empty-state illustration instead.
---
## Complete page example
**`Templates/UserListPage.htmx`**
```html
<div class="max-w-3xl mx-auto py-10">
<h1 class="text-2xl font-bold mb-6">Users</h1>
<div id="user-list"
hx-get="/users/data"
hx-trigger="load"
hx-swap="outerHTML">
$$LoadingSkeleton$$
</div>
</div>
```
**`Templates/UserListPage.htmx.cs`**
```csharp
namespace Htmx.ApiDemo.Templates;
public sealed class UserListPage : UserListPageBase
{
private readonly byte[] _skeleton;
public UserListPage()
{
// Build a table-shaped skeleton: 5 rows × 3 columns
var row = new System.Text.StringBuilder();
for (int i = 0; i < 5; i++)
{
row.Append("""<div class="flex gap-4 py-3 border-b">""");
row.Append(SkeletonHtml("h-4 w-1/3"));
row.Append(SkeletonHtml("h-4 w-1/4"));
row.Append(SkeletonHtml("h-4 w-1/5"));
row.Append("</div>");
}
_skeleton = row.ToString().ToUtf8Bytes();
}
private static string SkeletonHtml(string classes)
{
var buf = new System.Buffers.ArrayBufferWriter<byte>();
new Components.Skeleton(classes).Render(new HtmxRenderContext(buf));
return System.Text.Encoding.UTF8.GetString(buf.WrittenSpan);
}
protected override void RenderLoadingSkeleton(HtmxRenderContext ctx)
=> ctx.Writer.WriteUtf8(_skeleton);
}
```
**GET handlers**
```csharp
// Shell page — renders immediately with skeleton placeholder
[Handler]
[MapGet("/users")]
public static partial class GetUsersShellHandler
{
public record Query();
private static Task<IResult> HandleAsync(Query _, HttpContext ctx, CancellationToken ct)
=> ctx.WriteHtmxPage(new UserListPage(), title: "Users");
}
// Data endpoint — HTMX swaps this in place of the skeleton
[Handler]
[MapGet("/users/data")]
public static partial class GetUsersDataHandler
{
public record Query();
private static async Task<IResult> HandleAsync(
Query _, HttpContext ctx, MongoDbService db, CancellationToken ct)
{
var users = await db.GetAllUsersAsync(ct);
var table = new Components.Table(
headers: new[] { "Name", "Email", "Role" },
rows: users.Select(u => new[]
{
System.Net.WebUtility.HtmlEncode(u.DisplayName ?? ""),
System.Net.WebUtility.HtmlEncode(u.Email),
"user",
}));
var buf = new System.Buffers.ArrayBufferWriter<byte>();
table.Render(new HtmxRenderContext(buf));
return Results.Content(
System.Text.Encoding.UTF8.GetString(buf.WrittenSpan), "text/html");
}
}
```