Visual Widgets

July 10, 2026 · View on GitHub

Shapes, cards, images, badges, progress rings, tables, avatars — the things that make your app look like it was designed on purpose.


Divider

A thin horizontal rule. Use it between sections.

proton.H5(ctx, "Section One")
proton.Gap(ctx, 8)
proton.Label(ctx, "Some content.")
proton.Gap(ctx, 12)
proton.Divider(ctx)
proton.Gap(ctx, 12)
proton.H5(ctx, "Section Two")
proton.Divider(ctx proton.Context)

LabeledDivider

Same as Divider but with a centered text label.

proton.LabeledDivider(ctx, "Advanced Settings")
proton.LabeledDivider(ctx, "")   // plain divider — same as Divider
proton.LabeledDivider(ctx proton.Context, label string)

Rect

A solid colored rectangle. Pass 0 for width or height to fill the available space on that axis.

// 100dp wide, 4dp tall accent bar
proton.Rect(ctx, proton.RGB(0x89b4fa), 100, 4)

// full width, 2dp tall separator
proton.Rect(ctx, proton.RGB(0x333344), 0, 2)

// fill all available space
proton.Rect(ctx, proton.RGB(0x1a1a2e), 0, 0)
proton.Rect(ctx proton.Context, c color.NRGBA, widthDp, heightDp float32)

RoundRect

Same as Rect but with rounded corners.

proton.RoundRect(ctx, proton.RGB(0x2a2a3e), 200, 60, 12)  // 12dp corner radius
proton.RoundRect(ctx, proton.RGB(0x4c566a), 0, 40, 20)    // full width, pill shape
proton.RoundRect(ctx proton.Context, c color.NRGBA, widthDp, heightDp, radiusDp float32)

Card

Content inside a padded, rounded-rectangle background with a subtle shadow. The go-to container for grouping related content.

proton.Card(ctx, proton.RGB(0x2a2a3e), 12, 16, func(ctx proton.Context) {
    proton.H6(ctx, "Card Title")
    proton.Gap(ctx, 4)
    proton.Label(ctx, "Card content goes here.")
    proton.Gap(ctx, 12)
    proton.Pad(ctx, 4, func(ctx proton.Context) {
        if proton.Button(ctx, &u.btn, "Action") { doThing() }
    })
})
proton.Card(ctx proton.Context, bg color.NRGBA, cornerDp, padDp float32, content func(proton.Context))
  • bg — background color
  • cornerDp — corner radius (8–12 looks good for most cards)
  • padDp — padding between the card edge and the content

HoverCard

A card that changes background color on hover. Returns true if clicked.

if proton.HoverCard(ctx, &u.cardBtn,
    proton.RGB(0x2e3440),  // normal background
    proton.RGB(0x3b4252),  // hover background
    8,                     // corner radius dp
    func(ctx proton.Context) {
        proton.PadV(ctx, 10, func(ctx proton.Context) {
            proton.PadH(ctx, 12, func(ctx proton.Context) {
                proton.Label(ctx, "Click this card")
            })
        })
    },
) {
    println("card clicked")
}
proton.HoverCard(ctx proton.Context, state *proton.Clickable, bg, hover color.NRGBA, cornerDp float32, content func(proton.Context)) bool

Badge

A small rounded chip with text. For status labels, tags, counts, anything that needs a colored pill.

proton.Badge(ctx, proton.RGB(0x5e81ac), proton.RGB(0xeceff4), "stable")
proton.Badge(ctx, proton.RGB(0xa3be8c), proton.RGB(0x2e3440), "passing")
proton.Badge(ctx, proton.RGB(0xbf616a), proton.RGB(0xeceff4), "failing")
proton.Badge(ctx proton.Context, bg, fg color.NRGBA, text string)

Badges in a row:

proton.Row(ctx,
    func(ctx proton.Context) { proton.Badge(ctx, proton.RGB(0x5e81ac), proton.RGB(0xeceff4), "Go") },
    func(ctx proton.Context) { proton.Gap(ctx, 5) },
    func(ctx proton.Context) { proton.Badge(ctx, proton.RGB(0xa3be8c), proton.RGB(0x2e3440), "v1.0") },
    func(ctx proton.Context) { proton.Gap(ctx, 5) },
    func(ctx proton.Context) { proton.Badge(ctx, proton.RGB(0xebcb8b), proton.RGB(0x2e3440), "MIT") },
)

StatusDot

A small colored circle. Online/offline indicators, build status, anything that needs a colored dot next to some text.

proton.Row(ctx,
    func(ctx proton.Context) { proton.StatusDot(ctx, proton.RGB(0x4ade80), 9) },
    func(ctx proton.Context) { proton.Gap(ctx, 6) },
    func(ctx proton.Context) { proton.Caption(ctx, "Connected") },
)
proton.StatusDot(ctx proton.Context, c color.NRGBA, sizeDp float32)

Avatar

A circular badge showing initials. For user profile pictures when no image is available — which is most of the time.

proton.Avatar(ctx, "AJ", proton.RGB(0x5e81ac), proton.RGB(0xeceff4), 40)
proton.Avatar(ctx, "BC", proton.RGB(0xa3be8c), proton.RGB(0x2e3440), 32)
proton.Avatar(ctx proton.Context, initials string, bg, fg color.NRGBA, sizeDp float32)

ProgressRing

A circular progress indicator. Good for stat cards and dashboards where the circular shape communicates percentage more visually than a bar does.

proton.ProgressRing(ctx, 0.72, 48, 5, proton.RGB(0x88c0d0))
//                       ^     ^   ^   ^
//                  progress  sz  strokeW  color
proton.ProgressRing(ctx proton.Context, progress, sizeDp, strokeDp float32, c color.NRGBA)

progress is 0.0–1.0. sizeDp is the diameter. strokeDp is the ring thickness — 4–6dp looks good for most sizes.


Table

A data table with a header row and alternating row shading.

proton.Table(ctx,
    []string{"Name", "Role", "Status"},
    []proton.TableRow{
        {"Alice", "Engineer", "Active"},
        {"Bob",   "Designer", "Away"},
        {"Carol", "PM",       "Active"},
    },
)
proton.Table(ctx proton.Context, columns []string, rows []proton.TableRow)

proton.TableRow is just []string. Columns are equally wide.


Stepper

A horizontal step-progress indicator for multi-step flows.

proton.Stepper(ctx, 1, []string{"Account", "Profile", "Payment", "Done"})
//                  ^
//              current step (0-based)
proton.Stepper(ctx proton.Context, current int, steps []string)

Step 0 is the first step. Completed steps (index < current) get a filled accent color. The current step is highlighted. Future steps are dim.


Tooltip

A small label that appears when the user hovers over something.

type UI struct {
    saveHover proton.Clickable  // for tracking hover — separate from the button's Clickable
    saveBtn   proton.Clickable
}

proton.Tooltip(ctx, &u.saveHover, "Saves your work to disk (Ctrl+S)", func(ctx proton.Context) {
    proton.Pad(ctx, 4, func(ctx proton.Context) {
        if proton.Button(ctx, &u.saveBtn, "Save") {
            save()
        }
    })
})
proton.Tooltip(ctx proton.Context, state *proton.Clickable, tip string, content func(proton.Context))

The state Clickable tracks hover for the tooltip area. It's separate from any button inside the content — declare a dedicated one for each tooltip.


Images

Load once at startup. Draw every frame.

// load at startup — not in the draw function
img, err := proton.LoadImage("photo.png")
if err != nil {
    log.Fatal(err)
}

// in the draw function
proton.Image(ctx, img, 200, 150)  // 200dp wide, 150dp tall
proton.Image(ctx, img, 0, 0)      // natural pixel size
proton.LoadImage(path string) (proton.ImageOp, error)
proton.Image(ctx proton.Context, img proton.ImageOp, widthDp, heightDp float32)

PNG and JPEG are both supported.


Your app logo, loaded once and drawn anywhere. See 07-theming.md for the full setup. The short version:

//go:embed logo.png
var logoBytes []byte

// at startup
a.SetLogoBytes(logoBytes)

// in the draw function
proton.Logo(ctx, 48, 48)
proton.Logo(ctx proton.Context, widthDp, heightDp float32)
proton.HasLogo(ctx proton.Context) bool

CodeBlock

Monospace text in a rounded bordered box. For showing commands, file paths, snippets — anything the user is likely to copy.

proton.CodeBlock(ctx, "go get github.com/CzaxStudio/proton")
proton.CodeBlock(ctx, `a.Window("App", 480, 300, draw)
a.Run()`)
proton.CodeBlock(ctx proton.Context, code string)

ShortcutHint

A small keyboard badge. Show these next to menu items or button labels to communicate keyboard shortcuts.

proton.Row(ctx,
    func(ctx proton.Context) { proton.Label(ctx, "Save") },
    func(ctx proton.Context) { proton.Gap(ctx, 8) },
    func(ctx proton.Context) { proton.ShortcutHint(ctx, "Ctrl+S") },
)
proton.ShortcutHint(ctx proton.Context, keys string)

ColorSwatch

A row of colored circles the user can click to select a color. Returns the index of the selected one, or -1 if none selected yet.

type UI struct {
    swatches     [6]proton.Clickable
    chosenColor  int
}

palette := []color.NRGBA{
    proton.RGB(0xf87171),
    proton.RGB(0xfbbf24),
    proton.RGB(0x4ade80),
    proton.RGB(0x60a5fa),
    proton.RGB(0xa78bfa),
    proton.RGB(0xf472b6),
}

i := proton.ColorSwatch(ctx, u.swatches[:], palette, u.chosenColor, 26)
if i >= 0 {
    u.chosenColor = i
}
proton.ColorSwatch(ctx proton.Context, btns []proton.Clickable, colors []color.NRGBA, selected int, sizeDp float32) int

The selected circle gets a ring around it.