Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.

Bar demo

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():

PresetCharactersDescription
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

Bar styles

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:

ConstantLayout
BarAlignRightPadINF ⏳ Downloading [━━━━━╸╺──────] 45% (default)
BarAlignLeftPadINF ⏳ [━━━━━╸╺──────] 45% Downloading
BarAlignInlineINF ⏳ Downloading [━━━━━╸╺──────] 45%
BarAlignRightINF ⏳ Downloading [━━━━━╸╺──────] 45%
BarAlignLeftINF ⏳ [━━━━━╸╺──────] 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:

WidgetDescription
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")
WidgetNoneAlways returns “” - suppresses default percent
WidgetSeparator(s)Always renders s - use as a divider inside Widgets

All widget constructors accept WidgetOption values:

OptionApplies toDescription
WithDigits(n)WidgetPercent, WidgetBytes, WidgetIBytes, WidgetBytesRate, WidgetIBytesRatePrecision: significant digits or decimal places
WithUnit(label)WidgetRateUnit label (e.g. "ops""150 ops/s")
WithStyle(s)all widgetsLipgloss 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")