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

158 lines
4.6 KiB
Markdown

# Progress
A horizontal bar that fills from left to right to show how complete something is. Use it for upload progress, onboarding checklists, storage usage, or anything that has a percentage value.
---
## Quick example
```csharp
new Progress(value: 72)
```
---
## All the options
```csharp
public Progress(int value, string size = "default")
```
| Parameter | What it does |
|---|---|
| `value` | How filled the bar is, from 0 to 100. Values outside this range are clamped automatically. |
| `size` | Height of the bar: `"sm"` (6px), `"default"` (10px), or `"lg"` (16px). |
---
## Real-world examples
### Disk usage inside a Card
```csharp
// Pre-render the Progress bar to HTML
var w = new System.Buffers.ArrayBufferWriter<byte>();
new Progress(value: usedPercent, size: "lg").Render(new HtmxRenderContext(w));
var progressHtml = System.Text.Encoding.UTF8.GetString(w.WrittenSpan);
new Card(
title: "Storage",
content: $"""
<div class="mb-2 flex justify-between text-sm">
<span>Used</span>
<span>{usedGb} GB / {totalGb} GB</span>
</div>
{progressHtml}
""")
```
### Live progress bar (HTMX polling)
Wrap the component in a polling `<div>` that swaps the fragment every second:
```html
<div id="job-progress"
hx-get="/jobs/42/progress"
hx-trigger="every 1s"
hx-swap="outerHTML">
$$ProgressBar$$
</div>
```
The handler returns a fresh render of the component with the updated value. The `transition-all` CSS on the fill makes the change smooth.
### Three sizes side by side
```csharp
new Progress(value: 40, size: "sm") // compact, good for table rows
new Progress(value: 60) // standard
new Progress(value: 80, size: "lg") // prominent
```
---
## How it works
Progress is two nested `<div>` elements. The outer one is the grey track; the inner one is the filled bar. The fill width is set as an inline `style="width: {value}%"` so no JavaScript is required. The `transition-all` class makes the bar animate smoothly when the value changes via an HTMX swap.
---
## Complete page example
**`Templates/JobStatusPage.htmx`**
```html
<div class="max-w-md mx-auto py-10">
<h1 class="text-2xl font-bold mb-2">Processing</h1>
<p class="text-sm text-muted-foreground mb-6">$$StatusText$$</p>
<div class="mb-2 flex justify-between text-sm">
<span>Progress</span>
<span>$$ProgressLabel$$</span>
</div>
$$ProgressBar$$
$$DoneAlert$$
</div>
```
**`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<IResult> 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");
}
}
```