Bar
Bar creates a determinate progress bar that shows filled/empty cells and a live percentage. Use SetProgress on the ProgressUpdate to advance the bar.

err := clog.Bar("Downloading", 100).
Str("file", "release.tar.gz").
Elapsed("elapsed").
Progress(ctx, func(ctx context.Context, p *clog.ProgressUpdate) error {
for i := range 101 {
p.SetProgress(i).Msg("Downloading").Send()
time.Sleep(20 * time.Millisecond)
}
return nil
}).
Prefix("✅").
Msg("Download complete")
// INF ⏳ Downloading [━━━━━━━━╸───────────] 42% elapsed=1.2s
// INF ✅ Download complete file=release.tar.gz elapsed=3.4s
SetTotal can be called mid-task to update the denominator if the total becomes known after the task starts:
p.SetProgress(50).SetTotal(200).Msg("Processing").Send()
AddTotal atomically adds to the total - useful for “discovered more work” patterns where the full scope isn’t known upfront:
p.AddTotal(50) // discovered 50 more items
p.AddTotal(-10) // 10 items turned out to be duplicates
Styles
Six pre-built styles are available in progress_bar_presets.go. Pass any of them to .Style():
| Preset | Characters | Description |
|---|---|---|
BarBasic | [=====> ] | ASCII-only for maximum compatibility |
BarDash | [----- ] | Simple dash fill |
BarThin | [━━━╺──────] | Box-drawing with half-cell resolution (default) |
BarBlock | │█████░░░░░│ | Solid block characters |
BarGradient | │██████▍ │ | Block elements with 8x sub-cell resolution |
BarSmooth | │████▌ │ | Block characters with half-block leading edge |

clog.Bar("Uploading", total).
Style(clog.BarSmooth).
Progress(ctx, task).
Msg("Done")
BarThin and BarSmooth use half-cell resolution via HalfFilled (and HalfEmpty for BarThin), giving twice the visual granularity of full-cell styles. BarGradient uses GradientFill for 8x sub-cell resolution - the smoothest built-in option.
Custom Style
Build a fully custom style by passing a BarStyle struct:
clog.Bar("Uploading", total).
Style(clog.BarStyle{
Align: clog.BarAlignInline, // inline with message (default: BarAlignRightPad)
CapStyle: new(lipgloss.NewStyle().Bold(true)), // style for [ ] caps (default: bold white)
CapLeft: "|",
CapRight: "|",
CharEmpty: '-',
CharFill: '=',
CharHead: '>', // decorative head at leading edge (0 = disabled)
HalfEmpty: 0, // half-cell trailing edge for 2x resolution (0 = disabled)
HalfFilled: 0, // half-cell leading edge for 2x resolution (0 = disabled)
Separator: " ", // separator between message, bar, and widget text
StyleEmpty: new(lipgloss.NewStyle().Foreground(lipgloss.Color("8"))), // grey
StyleFill: new(lipgloss.NewStyle().Foreground(lipgloss.Color("2"))), // green
WidgetLeft: clog.WidgetPercent(clog.WithDigits(1)), // "50.0%" to the left of the bar
WidgetRight: clog.WidgetNone, // suppress the default right-side percent
Width: 30, // fixed inner width (0 = auto-size from terminal)
WidthMin: 10, // auto-size minimum (default 10)
WidthMax: 40, // auto-size maximum (default 40)
}).
Progress(ctx, task).
Msg("Done")
When Width is 0, the bar auto-sizes to one quarter of the terminal width, clamped to [WidthMin, WidthMax].
All presets include bold white CapStyle for the bar caps. Set CapStyle to nil for unstyled caps.
Progress Gradient
Color the bar fill based on progress using ProgressGradient. The filled portion shifts through the gradient as progress advances (e.g. red at 0%, yellow at 50%, green at 100%):
style := clog.BarBlock
style.ProgressGradient = clog.DefaultBarGradient() // red → yellow → green
clog.Bar("Building", 100).
Style(style).
Progress(ctx, task).
Msg("Built")
Custom gradients work the same as other gradient fields:
style.ProgressGradient = []clog.ColorStop{
{Position: 0, Color: colorful.Color{R: 0.3, G: 0.3, B: 1}}, // blue
{Position: 0.5, Color: colorful.Color{R: 1, G: 1, B: 1}}, // white
{Position: 1, Color: colorful.Color{R: 0.3, G: 1, B: 0.3}}, // green
}
When set, ProgressGradient overrides the StyleFill foreground color. Use DefaultBarGradient() to get the default red → yellow → green stops.
Alignment
The Align field on BarStyle controls where the bar appears on the line:
| Constant | Layout |
|---|---|
BarAlignRightPad | INF ⏳ Downloading [━━━━━╸╺──────] 45% (default) |
BarAlignLeftPad | INF ⏳ [━━━━━╸╺──────] 45% Downloading |
BarAlignInline | INF ⏳ Downloading [━━━━━╸╺──────] 45% |
BarAlignRight | INF ⏳ Downloading [━━━━━╸╺──────] 45% |
BarAlignLeft | INF ⏳ [━━━━━╸╺──────] 45% Downloading |
The padded variants (BarAlignRightPad, BarAlignLeftPad) fill the gap between message and bar with spaces to span the terminal width. When the terminal is too narrow, they fall back to the Separator between parts.
Widgets
WidgetLeft and WidgetRight on BarStyle control text annotations beside the bar. Each is a BarWidget - a callback that receives progress state and returns a string:
type BarState struct {
Current int
Total int
Elapsed time.Duration
Rate float64 // items per second
}
type BarWidget func(BarState) string
All presets set WidgetRight: WidgetPercent(0) - padded percentage on the right (e.g. " 42%"). When both widgets are nil, the same default applies.
Built-in widgets:
| Widget | Description |
|---|---|
WidgetPercent(n) | Padded percentage with n decimal places |
WidgetBytes() | SI byte progress (e.g. " 50 MB / 100 MB", base-1000) |
WidgetIBytes() | IEC byte progress (e.g. "50 MiB / 100 MiB", base-1024) |
WidgetETA() | Estimated time remaining (e.g. "ETA 2m30s", "ETA ∞") |
WidgetRate() | Items per second (e.g. "150/s", "1.5k/s") |
WidgetBytesRate() | SI byte throughput (e.g. "82.9 MB/s") |
WidgetIBytesRate() | IEC byte throughput (e.g. "82.9 MiB/s") |
WidgetNone | Always returns “” - suppresses default percent |
WidgetSeparator(s) | Always renders s - use as a divider inside Widgets |
All widget constructors accept WidgetOption values:
| Option | Applies to | Description |
|---|---|---|
WithDigits(n) | WidgetPercent, WidgetBytes, WidgetIBytes, WidgetBytesRate, WidgetIBytesRate | Precision: significant digits or decimal places |
WithUnit(label) | WidgetRate | Unit label (e.g. "ops" → "150 ops/s") |
WithStyle(s) | all widgets | Lipgloss style applied to the widget’s output |
Composing multiple widgets:
Use Widgets to combine several widgets on a single side. Empty outputs are filtered and the rest are joined with a space:
style := clog.BarThin
style.WidgetRight = clog.Widgets(clog.WidgetETA(), clog.WidgetRate())
// INF ⏳ Processing [━━━━━╸╺──────] ETA 2m30s 150/s
Add a visual divider with WidgetSeparator:
style.WidgetRight = clog.Widgets(
clog.WidgetETA(),
clog.WidgetSeparator("│"),
clog.WidgetRate(),
)
// INF ⏳ Processing [━━━━━╸╺──────] ETA 2m30s │ 150/s
Pass WithStyle to any widget to apply a Lipgloss style to its output:
faint := new(lipgloss.NewStyle().Faint(true))
style.WidgetRight = clog.Widgets(
clog.WidgetETA(clog.WithStyle(faint)),
clog.WidgetSeparator("│", clog.WithStyle(faint)),
clog.WidgetRate(clog.WithStyle(faint)),
)
// INF ⏳ Processing [━━━━━╸╺──────] ETA 2m30s │ 150/s (all rendered faint)
Move percent to the left:
style := clog.BarThin
style.WidgetLeft = clog.WidgetPercent(0)
style.WidgetRight = clog.WidgetNone
Download progress with byte sizes:
fileSize := 150 * 1000 * 1000 // 150 MB
style := clog.BarSmooth
style.WidgetRight = clog.WidgetBytes()
clog.Bar("Downloading", fileSize).
Style(style).
Str("file", "model.bin").
Progress(ctx, func(ctx context.Context, p *clog.ProgressUpdate) error {
// p.SetProgress(bytesReceived).Send()
}).
Msg("Downloaded")
// INF ⏳ Downloading │████▌ │ 75 MB / 150 MB file=model.bin
The current value is right-aligned to the total’s width to prevent the bar from jumping as digits change. Use WidgetIBytes() for base-1024 units (KiB, MiB, GiB).
ETA and rate:
style := clog.BarBlock
style.WidgetLeft = clog.WidgetETA()
style.WidgetRight = clog.WidgetRate(clog.WithUnit("items"))
clog.Bar("Processing", 500).
Style(style).
Progress(ctx, task).
Msg("Done")
// INF ⏳ Processing ETA 2m30s │█████░░░░░│ 150 items/s
WidgetETA shows "ETA ∞" before any progress, a countdown during the task, and "" when complete. WidgetRate accepts an optional WithUnit for a label; without it, output is "150/s".
Byte throughput:
style := clog.BarGradient
style.WidgetRight = clog.WidgetBytesRate()
clog.Bar("Uploading", totalBytes).
Style(style).
Progress(ctx, task).
Msg("Done")
// INF ⏳ Uploading │██████▍ │ 82.9 MB/s
Use WidgetIBytesRate() for IEC units (MiB/s). Both accept WithDigits(n) to control significant digits (default 3).
Custom widget:
style := clog.BarThin
style.WidgetRight = func(s clog.BarState) string {
remaining := s.Total - s.Current
return fmt.Sprintf("%d remaining", remaining)
}
Suppress percent entirely:
style := clog.BarThin
style.WidgetRight = clog.WidgetNone
Percentage as a Field
Use .BarPercent(key) on the builder to move the percentage into structured fields. This suppresses the default right-side widget:
clog.Bar("Installing", 100).
BarPercent("progress").
Elapsed("elapsed").
Progress(ctx, task).
Msg("Installed")
// INF ⏳ Installing [━━━━━╸╺──────] progress=45% elapsed=1.2s
All animations gracefully degrade: when colours are disabled (CI, piped output), a static status line with an ⏳ prefix is printed instead.
The icon displayed during Pulse, Shimmer, and Bar animations defaults to ⏳ and can be changed with .Prefix() on the builder:
clog.Pulse("Warming up").
Prefix("🔄").
Wait(ctx, action).
Msg("Ready")