# 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 `
` — you shape it entirely through CSS classes. --- ## Real-world examples ### A card loading state (avatar + two text lines) ```html
$$AvatarSkeleton$$
$$Line1$$ $$Line2$$
``` ```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
$$UserListSkeleton$$
``` 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(); 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 `
` 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`). - 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

Users

$$LoadingSkeleton$$
``` **`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("""
"""); 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("
"); } _skeleton = row.ToString().ToUtf8Bytes(); } private static string SkeletonHtml(string classes) { var buf = new System.Buffers.ArrayBufferWriter(); 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 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 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(); table.Render(new HtmxRenderContext(buf)); return Results.Content( System.Text.Encoding.UTF8.GetString(buf.WrittenSpan), "text/html"); } } ```