# Progress A horizontal progress bar. Value is clamped to 0–100. Three sizes control the bar height. --- ## HTML structure ``` div.w-full.bg-secondary.rounded-full.overflow-hidden.{size class} div.bg-primary.rounded-full.h-full.transition-all[style="width: {value}%"] ``` --- ## CSS mechanics | Class | Effect | |---|---| | `bg-secondary` | Neutral track color | | `bg-primary` | Filled indicator color | | `rounded-full overflow-hidden` | Pill-shaped track; fills also become pill-shaped | | `transition-all` | Smooth animation when `width` changes | **Size classes applied to the outer track:** | Size | Class | Height | |---|---|---| | `sm` | `h-1.5` | 6 px | | `default` | `h-2.5` | 10 px | | `lg` | `h-4` | 16 px | --- ## Constructor signature ```csharp public Progress(int value, string size = "default") ``` | Parameter | Description | |---|---| | `value` | Fill percentage; clamped to 0–100 | | `size` | `"sm"` / `"default"` / `"lg"` | --- ## Usage examples ### Inline usage ```csharp new Progress(value: 72) new Progress(value: 40, size: "sm") new Progress(value: 100, size: "lg") ``` ### Inside a Card ```csharp new Card( title: "Disk usage", content: $"""
Used {used} GB / {total} GB
{progressHtml} """) ``` (Pre-render the `Progress` to a string using `HtmxRenderContext` and `ArrayBufferWriter`.) ### HTMX live update ```html
$$ProgressBar$$
``` The endpoint returns a partial re-render of this fragment with the updated `value`. --- ## Tips and tricks - Values below 0 are treated as 0; values above 100 are treated as 100 — no manual clamping needed. - Use `size: "sm"` for compact UI areas such as table rows. - To animate progress smoothly, let `transition-all` do the work: re-render the component via HTMX on a polling interval or push updates via SSE. - For an indeterminate spinner, use `Skeleton` instead (it has `animate-pulse` built in). - For an indeterminate spinner, use `Skeleton` instead (it has `animate-pulse` built in). --- ## Complete page example **`Templates/JobStatusPage.htmx`** ```html

Processing

$$StatusText$$

Progress $$ProgressLabel$$
$$ProgressBar$$ $$DoneAlert$$
``` **`Templates/JobStatusPage.htmx.cs`** ```csharp namespace Htmx.ApiDemo.Templates; public sealed class JobStatusPage : JobStatusPageBase { private readonly byte[] _statusText; private readonly byte[] _progressLabel; private readonly IHtmxComponent _progressBar; private readonly IHtmxComponent _doneAlert; public JobStatusPage(int percent, string statusText) { _statusText = System.Net.WebUtility.HtmlEncode(statusText).ToUtf8Bytes(); _progressLabel = $"{percent}%".ToUtf8Bytes(); _progressBar = new Components.Progress(value: percent); _doneAlert = percent >= 100 ? new Components.Alert(title: "Complete!", description: "Your export is ready.") : HtmxEmpty.Instance; } protected override void RenderStatusText(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_statusText); protected override void RenderProgressLabel(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_progressLabel); protected override void RenderProgressBar(HtmxRenderContext ctx) => _progressBar.Render(ctx.Next()); protected override void RenderDoneAlert(HtmxRenderContext ctx) => _doneAlert.Render(ctx.Next()); } ``` **GET handler with HTMX polling** ```csharp [Handler] [MapGet("/jobs/{jobId}/status")] public static partial class GetJobStatusHandler { public record Query([property: FromRoute] string JobId); private static async Task HandleAsync( Query q, HttpContext ctx, JobQueue jobs, CancellationToken ct) { var job = await jobs.GetAsync(q.JobId, ct); if (job is null) return Results.NotFound(); var page = new JobStatusPage(job.PercentComplete, job.StatusText); // If polling (HTMX partial), only return the progress fragment if (ctx.Request.Headers.ContainsKey("HX-Request")) { // Stop polling when done if (job.PercentComplete >= 100) ctx.Response.Headers.Append("HX-Trigger", "jobComplete"); return await ctx.WriteHtmxPage(page, title: "Processing"); } // Full page load — include polling trigger ctx.Response.Headers.Append("HX-Trigger-After-Settle", """{"startPolling": {"interval": 1000, "target": "#progress-region"}}"""); return await ctx.WriteHtmxPage(page, title: "Processing"); } } ```