Progress
Progress indicators inform users about the status of ongoing processes.
TailwindCSS
package showcase
import "github.com/axzilla/templui/components"
templ ProgressDefault() {
<div class="w-full max-w-sm">
@components.Progress(components.ProgressProps{
Value: 25,
})
</div>
}
package components
import (
"fmt"
"github.com/axzilla/templui/utils"
)
type ProgressSize string
type ProgressVariant string
const (
ProgressSizeDefault ProgressSize = ""
ProgressSizeSm ProgressSize = "sm"
ProgressSizeLg ProgressSize = "lg"
)
const (
ProgressVariantDefault ProgressVariant = "default"
ProgressVariantSuccess ProgressVariant = "success"
ProgressVariantDanger ProgressVariant = "danger"
ProgressVariantWarning ProgressVariant = "warning"
)
type ProgressProps struct {
ID string
Class string
Attributes templ.Attributes
Max int
Value int
Label string
ShowValue bool
Size ProgressSize
Variant ProgressVariant
BarClass string
HxGet string
HxTrigger string
HxTarget string
HxSwap string
}
templ Progress(props ...ProgressProps) {
{{ var p ProgressProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
if p.ID == "" {
{{ p.ID = utils.RandomID() }}
}
<div
id={ p.ID }
class={ utils.TwMerge("w-full", p.Class) }
if p.HxGet != "" {
hx-get={ p.HxGet }
}
if p.HxTrigger != "" {
hx-trigger={ p.HxTrigger }
}
if p.HxTarget != "" {
hx-target={ p.HxTarget }
}
if p.HxSwap != "" {
hx-swap={ p.HxSwap }
}
aria-valuemin="0"
aria-valuemax={ fmt.Sprintf("%d", getMaxValue(p.Max)) }
aria-valuenow={ fmt.Sprintf("%d", p.Value) }
role="progressbar"
{ p.Attributes... }
>
if p.Label != "" || p.ShowValue {
<div class="flex justify-between items-center mb-1">
if p.Label != "" {
<span class="text-sm font-medium">{ p.Label }</span>
}
if p.ShowValue {
<span class="text-sm font-medium">
{ fmt.Sprintf("%d%%", getProgressPercentage(p.Value, p)) }
</span>
}
</div>
}
<div class="w-full overflow-hidden rounded-full bg-secondary">
<div
class={
utils.TwMerge(
"h-full rounded-full transition-all",
getProgressSizeClasses(p.Size),
getProgressVariantClasses(p.Variant),
p.BarClass,
),
}
style={ fmt.Sprintf("width: %d%%", getProgressPercentage(p.Value, p)) }
></div>
</div>
</div>
}
func getMaxValue(max int) int {
if max <= 0 {
return 100
}
return max
}
func getProgressPercentage(value int, props ProgressProps) int {
max := getMaxValue(props.Max)
if value < 0 {
value = 0
}
if value > max {
value = max
}
return (value * 100) / max
}
func getProgressSizeClasses(size ProgressSize) string {
switch size {
case ProgressSizeSm:
return "h-1"
case ProgressSizeLg:
return "h-4"
default:
return "h-2.5"
}
}
func getProgressVariantClasses(variant ProgressVariant) string {
switch variant {
case ProgressVariantSuccess:
return "bg-green-500"
case ProgressVariantDanger:
return "bg-destructive"
case ProgressVariantWarning:
return "bg-yellow-500"
default:
return "bg-primary"
}
}
Usage
@components.Progress(components.ProgressProps{...})
Examples
Sizes
package showcase
import "github.com/axzilla/templui/components"
templ ProgressSizes() {
<div class="space-y-6 w-full max-w-sm">
@components.Progress(components.ProgressProps{
Value: 50,
Size: components.ProgressSizeSm,
})
@components.Progress(components.ProgressProps{
Value: 65,
Size: components.ProgressSizeLg,
})
@components.Progress(components.ProgressProps{
Value: 80,
Size: components.ProgressSizeLg,
})
</div>
}
package components
import (
"fmt"
"github.com/axzilla/templui/utils"
)
type ProgressSize string
type ProgressVariant string
const (
ProgressSizeDefault ProgressSize = ""
ProgressSizeSm ProgressSize = "sm"
ProgressSizeLg ProgressSize = "lg"
)
const (
ProgressVariantDefault ProgressVariant = "default"
ProgressVariantSuccess ProgressVariant = "success"
ProgressVariantDanger ProgressVariant = "danger"
ProgressVariantWarning ProgressVariant = "warning"
)
type ProgressProps struct {
ID string
Class string
Attributes templ.Attributes
Max int
Value int
Label string
ShowValue bool
Size ProgressSize
Variant ProgressVariant
BarClass string
HxGet string
HxTrigger string
HxTarget string
HxSwap string
}
templ Progress(props ...ProgressProps) {
{{ var p ProgressProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
if p.ID == "" {
{{ p.ID = utils.RandomID() }}
}
<div
id={ p.ID }
class={ utils.TwMerge("w-full", p.Class) }
if p.HxGet != "" {
hx-get={ p.HxGet }
}
if p.HxTrigger != "" {
hx-trigger={ p.HxTrigger }
}
if p.HxTarget != "" {
hx-target={ p.HxTarget }
}
if p.HxSwap != "" {
hx-swap={ p.HxSwap }
}
aria-valuemin="0"
aria-valuemax={ fmt.Sprintf("%d", getMaxValue(p.Max)) }
aria-valuenow={ fmt.Sprintf("%d", p.Value) }
role="progressbar"
{ p.Attributes... }
>
if p.Label != "" || p.ShowValue {
<div class="flex justify-between items-center mb-1">
if p.Label != "" {
<span class="text-sm font-medium">{ p.Label }</span>
}
if p.ShowValue {
<span class="text-sm font-medium">
{ fmt.Sprintf("%d%%", getProgressPercentage(p.Value, p)) }
</span>
}
</div>
}
<div class="w-full overflow-hidden rounded-full bg-secondary">
<div
class={
utils.TwMerge(
"h-full rounded-full transition-all",
getProgressSizeClasses(p.Size),
getProgressVariantClasses(p.Variant),
p.BarClass,
),
}
style={ fmt.Sprintf("width: %d%%", getProgressPercentage(p.Value, p)) }
></div>
</div>
</div>
}
func getMaxValue(max int) int {
if max <= 0 {
return 100
}
return max
}
func getProgressPercentage(value int, props ProgressProps) int {
max := getMaxValue(props.Max)
if value < 0 {
value = 0
}
if value > max {
value = max
}
return (value * 100) / max
}
func getProgressSizeClasses(size ProgressSize) string {
switch size {
case ProgressSizeSm:
return "h-1"
case ProgressSizeLg:
return "h-4"
default:
return "h-2.5"
}
}
func getProgressVariantClasses(variant ProgressVariant) string {
switch variant {
case ProgressVariantSuccess:
return "bg-green-500"
case ProgressVariantDanger:
return "bg-destructive"
case ProgressVariantWarning:
return "bg-yellow-500"
default:
return "bg-primary"
}
}
Colors
package showcase
import "github.com/axzilla/templui/components"
templ ProgressColors() {
<div class="space-y-6 w-full max-w-xs">
@components.Progress(components.ProgressProps{
Value: 50,
Variant: components.ProgressVariantSuccess,
})
@components.Progress(components.ProgressProps{
Value: 75,
Variant: components.ProgressVariantDanger,
})
@components.Progress(components.ProgressProps{
Value: 90,
Variant: components.ProgressVariantWarning,
})
</div>
}
package components
import (
"fmt"
"github.com/axzilla/templui/utils"
)
type ProgressSize string
type ProgressVariant string
const (
ProgressSizeDefault ProgressSize = ""
ProgressSizeSm ProgressSize = "sm"
ProgressSizeLg ProgressSize = "lg"
)
const (
ProgressVariantDefault ProgressVariant = "default"
ProgressVariantSuccess ProgressVariant = "success"
ProgressVariantDanger ProgressVariant = "danger"
ProgressVariantWarning ProgressVariant = "warning"
)
type ProgressProps struct {
ID string
Class string
Attributes templ.Attributes
Max int
Value int
Label string
ShowValue bool
Size ProgressSize
Variant ProgressVariant
BarClass string
HxGet string
HxTrigger string
HxTarget string
HxSwap string
}
templ Progress(props ...ProgressProps) {
{{ var p ProgressProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
if p.ID == "" {
{{ p.ID = utils.RandomID() }}
}
<div
id={ p.ID }
class={ utils.TwMerge("w-full", p.Class) }
if p.HxGet != "" {
hx-get={ p.HxGet }
}
if p.HxTrigger != "" {
hx-trigger={ p.HxTrigger }
}
if p.HxTarget != "" {
hx-target={ p.HxTarget }
}
if p.HxSwap != "" {
hx-swap={ p.HxSwap }
}
aria-valuemin="0"
aria-valuemax={ fmt.Sprintf("%d", getMaxValue(p.Max)) }
aria-valuenow={ fmt.Sprintf("%d", p.Value) }
role="progressbar"
{ p.Attributes... }
>
if p.Label != "" || p.ShowValue {
<div class="flex justify-between items-center mb-1">
if p.Label != "" {
<span class="text-sm font-medium">{ p.Label }</span>
}
if p.ShowValue {
<span class="text-sm font-medium">
{ fmt.Sprintf("%d%%", getProgressPercentage(p.Value, p)) }
</span>
}
</div>
}
<div class="w-full overflow-hidden rounded-full bg-secondary">
<div
class={
utils.TwMerge(
"h-full rounded-full transition-all",
getProgressSizeClasses(p.Size),
getProgressVariantClasses(p.Variant),
p.BarClass,
),
}
style={ fmt.Sprintf("width: %d%%", getProgressPercentage(p.Value, p)) }
></div>
</div>
</div>
}
func getMaxValue(max int) int {
if max <= 0 {
return 100
}
return max
}
func getProgressPercentage(value int, props ProgressProps) int {
max := getMaxValue(props.Max)
if value < 0 {
value = 0
}
if value > max {
value = max
}
return (value * 100) / max
}
func getProgressSizeClasses(size ProgressSize) string {
switch size {
case ProgressSizeSm:
return "h-1"
case ProgressSizeLg:
return "h-4"
default:
return "h-2.5"
}
}
func getProgressVariantClasses(variant ProgressVariant) string {
switch variant {
case ProgressVariantSuccess:
return "bg-green-500"
case ProgressVariantDanger:
return "bg-destructive"
case ProgressVariantWarning:
return "bg-yellow-500"
default:
return "bg-primary"
}
}
Integration Patterns
The Progress component can be integrated in different ways depending on your requirements:
File Uploads
For file uploads, use the browser's XMLHttpRequest or fetch API with progress events:
// Client-side JavaScript
const form = document.querySelector("#upload-form");
form.addEventListener("submit", (e) => {
e.preventDefault();
const formData = new FormData(form);
const xhr = new XMLHttpRequest();
// Update progress bar with upload progress
xhr.upload.addEventListener("progress", (event) => {
if (event.lengthComputable) {
const percent = Math.round((event.loaded / event.total) * 100);
// Update progress bar width
document.querySelector("#progress-bar").style.width = percent + "%";
// Update percentage text
document.querySelector("#progress-value").textContent = percent + "%";
}
});
xhr.open("POST", "/upload");
xhr.send(formData);
});
Multi-step Forms
For multi-step forms, you can use either client-side calculations or HTMX for server-side validation between steps:
// Client-side approach
<div data-current-step="1" data-total-steps="4">
@components.Progress(components.ProgressProps{
Value: 25, // 1 of 4 steps = 25%
Label: "Step 1 of 4",
ShowValue: true,
})
// Form steps
</div>
// HTMX approach
<button
hx-get="/form/step/2"
hx-target="#form-container"
class="px-4 py-2 bg-primary text-white rounded"
>
Next
</button>
Background Processes
For tracking background processes, choose between:
HTMX Polling: Simple approach, good for processes under a few minutes
@components.Progress(components.ProgressProps{ Value: initialValue, Label: "Processing...", ShowValue: true, HxGet: "/api/job/123/progress", HxTrigger: "every 2s", HxTarget: "#progress-container", })
Server-Sent Events (SSE): For real-time updates and longer processes
// Server-side Go code func SSEHandler(w http.ResponseWriter, r *http.Request) { // Set SSE headers w.Header().Set("Content-Type", "text/event-stream") w.Header().Set("Cache-Control", "no-cache") w.Header().Set("Connection", "keep-alive") // Send progress updates for i := 0; i <= 100; i += 10 { fmt.Fprintf(w, "data: {\"progress\": %d}\n\n", i) w.(http.Flusher).Flush() time.Sleep(1 * time.Second) } }