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

178 lines
5.2 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 grey placeholder that pulsates while real content is loading. Think of it as a rough pencil sketch of your UI — it shows the user where something will appear so the page feels responsive even before the data is ready.
---
## Quick example
```csharp
new Skeleton("h-4 w-3/4") // a loading line of text
new Skeleton("h-10 w-full") // a loading input field
new Skeleton("rounded-full h-12 w-12") // a loading avatar
```
---
## All the options
```csharp
public Skeleton(string classes = "")
```
| Parameter | What it does |
|---|---|
| `classes` | Tailwind classes that control the size and shape of the placeholder. |
There are no other parameters. The component itself is just an animated `<div>` — you shape it entirely through CSS classes.
---
## Real-world examples
### A card loading state (avatar + two text lines)
```html
<div class="flex items-center gap-4 p-6">
$$AvatarSkeleton$$
<div class="space-y-2 flex-1">
$$Line1$$
$$Line2$$
</div>
</div>
```
```csharp
_avatarSkeleton = new Skeleton("rounded-full h-10 w-10");
_line1 = new Skeleton("h-4 w-1/2");
_line2 = new Skeleton("h-4 w-3/4");
```
### HTMX swap: show skeleton immediately, replace with real content
Render the skeleton into a slot. HTMX fires immediately on page load and swaps it with the real content:
```html
<div id="user-list"
hx-get="/users"
hx-trigger="load"
hx-swap="outerHTML">
$$UserListSkeleton$$
</div>
```
The skeleton appears instantly; the data loads in the background and replaces it.
### A full table loading state
```csharp
// Stack five skeleton rows to simulate a loading table
var rows = string.Concat(Enumerable.Range(0, 5).Select(_ =>
{
var w = new System.Buffers.ArrayBufferWriter<byte>();
new Skeleton("h-8 w-full mb-2").Render(new HtmxRenderContext(w));
return System.Text.Encoding.UTF8.GetString(w.WrittenSpan);
}));
```
---
## How it works
Skeleton is a single `<div>` with `animate-pulse` (Tailwind's built-in pulsing animation) and `bg-muted`. You control the shape entirely through the `classes` parameter — use `h-*` and `w-*` for size, and `rounded-full` for circular shapes like avatars.
- `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");
}
}
```