Progress Bar

This example shows how to implement a smoothly scrolling progress bar.

We start with an initial state with a button that issues a POST to /start to begin the job:

<div
	{ hx.Target(htmx.TargetThis)... }
	{ hx.Swap(swap.OuterHTML)... }
>
	<h3>Start Progress</h3>
	<button { hx.Post("/examples/templ/progress-bar/job/")... }>
		Start Job
	</button>
</div>

This div is then replaced with a new div containing status and a progress bar that reloads itself every 600ms:

<div
	{ hx.Swap(swap.OuterHTML)... }
	{ hx.Trigger(shared.TriggerDone)... }
	{ hx.Get("/examples/templ/progress-bar/job/%d/", jobID)... }
	{ hx.Swap(swap.OuterHTML)... }
	{ hx.Target(htmx.TargetThis)... }
>
	<h3 role="status" id="pblabel" tabindex="-1" autofocus>
		Job { strconv.FormatInt(jobID, 10) } Running
	</h3>
	@ProgressFetcher(jobID, progress)
</div>
templ ProgressFetcher(jobID int64, progress int) {
	<div
		{ hx.Get("/examples/templ/progress-bar/job/%d/progress/", jobID)... }
		{ hx.TriggerExtended(trigger.Every(time.Millisecond * 600))... }
		{ hx.Target(htmx.TargetThis)... }
		{ hx.Swap(swap.InnerHTML)... }
	>
		@ProgressBar(progress)
	</div>
}

templ ProgressBar(progress int) {
	<div
		class="progress"
		role="progressbar"
		aria-valuemin="0"
		aria-valuemax="100"
		aria-valuenow={ strconv.Itoa(progress) }
		aria-labelledby="pblabel"
	>
		<div
			id="pb"
			class="progress-bar"
			{ progressWidth(progress)... }
		></div>
	</div>
}

func progressWidth(percent int) templ.Attributes {
	return templ.Attributes{
		"style": fmt.Sprintf("width: %d%%", percent),
	}
}

This progress bar is updated every 600 milliseconds, with the width style attribute and aria-valuenow attribute set to current progress value. Because there is an id on the progress bar div, htmx will smoothly transition between requests by settling the style attribute into its new value. This, when coupled with CSS transitions, makes the visual transition continuous rather than jumpy.

Finally, when the process is complete, a server returns a HX-Trigger: done header, which triggers an update of the UI to “Complete” state with a restart button added to the UI (we are using the class-tools extension in this example to add fade-in effect on the button):

<div
	{ hx.Target(htmx.TargetThis)... }
	{ hx.Swap(swap.OuterHTML)... }
>
	<h3 role="status" id="pblabel" tabindex="-1" autofocus>
		Job { strconv.FormatInt(jobID, 10) } Complete
	</h3>
	@ProgressBar(progress)
	<button
		id="restart-btn"
		{ hx.Post("/examples/templ/progress-bar/job/")... }
		{ classtools.Classes(hx, classtools.Add("show", time.Millisecond*600))... }
	>
		Restart Job
	</button>
</div>

This example uses styling cribbed from the bootstrap progress bar:

.progress-bar-demo .progress {
  height: 20px;
  margin-bottom: 20px;
  overflow: hidden;
  background-color: #f5f5f5;
  border-radius: 4px;
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
}
.progress-bar-demo .progress-bar {
  float: left;
  width: 0%;
  height: 100%;
  font-size: 12px;
  line-height: 20px;
  color: #fff;
  text-align: center;
  background-color: #337ab7;
  -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
  box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
  -webkit-transition: width 0.6s ease;
  -o-transition: width 0.6s ease;
  transition: width 0.6s ease;
}

Demo

Start Progress