Releases: go-widgets/toolkit
Release list
v0.9.2 — dark-theme readability fix (dim labels + Diff rows)
Bug-fix patch: dim labels and Diff Added/Removed rows now read correctly in dark themes.
What was broken
Two contrast bugs surfaced by the v0.9.1 gallery visual verification push:
-
Dim-label ink was invisible in dark themes. Widgets that render a subordinate/muted text tone (HeaderBar Subtitle, ActionRow Subtitle, Stat Title + Stat Flat-change, Timeline event Detail) pulled the ink from `theme.Border`. In the default light palette `Border = #C0C0C0` is a mid-grey visible on `Surface = #FFFFFF` so the dim label read correctly. In the default dark palette `Border = #3A3E46` sits deliberately close to `Surface = #1F2228` so the 1-px stroke around cards stays subtle — but that closeness makes the same Border tone almost invisible when used for TEXT on Surface.
-
Diff Added / Removed rows had light text on light backgrounds in dark themes. The row fills are fixed light-green / light-red (semantic colours — the "added lines are green" convention is stronger than any per-theme adaptation). The row text used `theme.OnSurface` which is dark in light themes (readable) but near-white in dark themes — near-invisible on the light-green / light-red row fill.
The fix
- New `dimInk(theme)` helper in `raster.go`: a 60/40 blend of `OnSurface` and `Surface`. Enough contrast against Surface to stay readable in any theme, less weight than `OnSurface` itself so it still forms a visual hierarchy.
- Replace `theme.Border` with `dimInk(theme)` at the four dim-label sites: `actionrow.go`, `headerbar.go`, `stat.go` (Title AND the Flat branch of `statChangeInk`), `timeline.go`.
- New fixed Diff inks: `diffAddedInk = {20,60,20}` (dark green) and `diffRemovedInk = {90,20,20}` (dark red). `Diff.Draw` picks the per-row ink based on `line.Kind`: Context still uses `theme.OnSurface`, Added / Removed lock in the fixed dark inks that pair correctly with the fixed light row backgrounds regardless of theme.
Compatibility
No API change — no new fields on Theme, no new exported functions, no signature changes. Consumers pinning v0.9.1 keep working; visual output improves on rebuild.
The fix is compositional: any future widget that wants a dim-label tone should call `dimInk(theme)` instead of reaching for `theme.Border`.
Verified
- `go vet ./...` clean
- `go test -cover ./...` → 100.0% of statements
- Cross-compile js/wasm + linux/arm64 + darwin/arm64 clean
- CI green
To pin:
```go
require github.com/go-widgets/toolkit v0.9.2
```
BSD-3-Clause.
v0.9.1 — chevron orientation fix
Bug-fix patch: DropDown / TreeView / Expander / Menu drew their triangle chevrons inverted from what the code comments described.
- DropDown drew ▲ instead of ▼.
- TreeView expanded chevron drew ▲ instead of ▼.
- TreeView collapsed chevron drew ◀ instead of ▶.
- Expander expanded drew ▲ instead of ▼; collapsed drew ◀ instead of ▶.
- Menu nested-item chevron drew ◀ instead of ▶.
The trace bug in each case was the same: the loop wrote the narrow (1-pixel) tip on the row/column expected to hold the wide (7- or 9-pixel) base and vice versa. Fix is one character per site — swap the sign of the loop index in the coordinate that varies along the chevron's axis.
Compatibility
No API change. Consumers pinning v0.9.0 keep working; visual output flips to the intended shape on rebuild.
Verified
- `go vet ./...` clean
- `go test -cover ./...` → 100.0% of statements
- Cross-compile js/wasm + linux/arm64 + darwin/arm64 clean
- CI green
To pin:
```go
require github.com/go-widgets/toolkit v0.9.1
```
BSD-3-Clause.
v0.9.0 — 8 new widgets, +15% surface (Wave 3 finishing pass)
Additive-only bump: 54 → 62 widgets. Every existing consumer keeps working unchanged.
Wave 3 (finishing pass) of the GTK / DaisyUI gap-closure campaign. Same recipe as v0.7.0 / v0.8.0: Base embedding, Draw(painter.Painter, *Theme), existing fillRect/strokeRect/DrawText helpers, theme.Extra["OnAccent"] fallback pattern.
Buttons
- SplitButton — primary action button with an attached arrow slot; independent
OnClick(main) andOnArrow(menu) callbacks,SplitButtonArrowW = 20visual separator;Arrow = falsecollapses to a wide accent button. - IconButton — compact single-glyph button for toolbars. Distinct from
Button(has text) andToggleButton(has toggle state). Rendered subtly ontheme.Surfacewith aBorderstroke, not onAccent— the toolbar-icon convention.
Data display
- Stat — DaisyUI-style KPI card: dim
Titleon top, bigValuein the middle (double-stroked to fake bold weight — a testable visible signal),Changeat the bottom with color derived fromStatTrend(StatFlat/StatUp/StatDown, fixed RGB per trend). - Timeline — vertical event log: rail line in
theme.Border, per-event colored marker (TimelineDefault/TimelineSuccess/TimelineWarning/TimelineError— mirrorsAlert's fixed RGBs),Titlenext to the marker, optional dimDetailline below.
Interactive
- DropZone — inline "drag files here" target with a dashed border (rendered as a series of short filled rects along each edge — the same abstraction level as the rest of the toolkit) +
Hoverstate that recolors border inAccenton drag-over.OnDropfires with[]string{ev.Code}on syntheticEventCharwithHover = true(host convention). - Chip — removable tag:
Text+ optional close-xaffordance.Closable = truereserves a right-side hit slot;EventMouseDownin that slot firesOnClose. Sibling toBadge(which is a counter/status pill with no close).
Composables
- FormField —
Labelabove aChildwidget, with mutually-exclusiveHelp(dim caption intheme.Border) orError(fixed red) below.Errortakes precedence overHelpwhen both are set. Events forward toChildwhen(X, Y)falls in its rect. - ProgressCircle — approximated circular progress: outer ring in
theme.SurfaceAlt("track"), inner square intheme.Surface("hole"), fill band scaled byFractionintheme.Accent,\"XX%\"centered in the hole intheme.OnSurface. Fraction clamps to[0, 1].
Development note
Built in parallel by 4 sub-agents in worktree isolation (/tmp/tk-w3-{a,b,c,d}). Each agent worked in its own fresh clone of v0.8.0; the 4 batches merged verbatim with no naming conflicts across the ~25 new package-level constants and enums, and 100.0% cov end-to-end. Third consecutive wave delivered by this pattern.
Verified
go vet ./...cleango test -cover ./...→ 100.0% of statements- Cross-compile smoke:
js/wasm,linux/arm64,darwin/arm64all clean - CI green
Compatibility
Additive only. No new theme fields, no new external deps beyond github.com/go-widgets/painter. No existing files touched.
To pin:
```go
require github.com/go-widgets/toolkit v0.9.0
```
Coverage now
DaisyUI 4 + libadwaita gap closure is materially complete. Remaining specialized widgets (Split view / DualPane variants, WindowControls, Aboutdialog, Snackbar-with-action) are either already covered by adjacent widgets or fall outside a pure-Go pixel-blitting library's scope.
License
BSD-3-Clause.
v0.8.0 — 12 new widgets, +29% surface
Additive-only bump: 42 → 54 widgets. Every existing consumer keeps working unchanged.
Wave 2 of the GTK / DaisyUI gap-closure campaign. Same recipe as v0.7.0: Base embedding, Draw(painter.Painter, *Theme), existing fillRect/strokeRect/DrawText helpers, theme.Extra["OnAccent"] fallback pattern.
Visual atoms
- Avatar — circular initials bubble; fill in a user-supplied
Colorortheme.Accentby default; ink via the OnAccent fallback. - Skeleton — loading placeholder in three flavors (
SkeletonText— NSurfaceAltbars with a natural 60%-width last line;SkeletonAvatar— same rounded shape asAvatar, no glyphs;SkeletonBlock— a single inset filled rect). - Rating — N-glyph selector; click routing on
EventMouseDowncomputes which star index landed and firesOnChange.
Overlays
- Toast — transient bottom-of-screen notification,
EventTick-drivenLifecountdown,ToastKindenum (Info/Success/Warning/Error), fixed RGB per kind mirroringAlert. - Banner — persistent full-width message with an optional action-button label + click callback +
Revealedstate (mirrorsAdwBanner). - Popover —
Visiblecontainer for aChildwidget with aSurfacefill,Borderstroke, and optionalTitleheader; forwardsEventMouseDowntoChildwith translated coordinates.
Structural
- ActionRow — libadwaita
AdwActionRow:Title(large) + optionalSubtitle(dim) + optionalPrefix/Suffixwidget slots. RoutesEventMouseDowntoPrefix/Suffixbased on X coordinate, likeHeaderBardoes withStart/End. - ViewSwitcher — segmented tab picker; current view highlighted in
Accentwith OnAccent ink; click routing computes segment index and firesOnChange. - ChatBubble — chat-message bubble with
Senderenum (ChatFromUserright-aligned inAccent,ChatFromOtherleft-aligned inSurfaceAlt), multi-line via '\n' split,ChatBubbleMaxWwidth cap.
Interactive / data
- SearchEntry — Entry variant with a
?prefix and a clear-xaffordance on the right (visible only whenText != \"\"); handlesEventChar,EventKeyBackspace,EventMouseDownon the clear slot. - Diff — colored line-by-line diff view;
DiffKind(DiffContext/DiffAdded/DiffRemoved), added rows in a fixed light-green RGB, removed in a fixed light-red RGB,ContextinSurface. - Pagination — prev / page-numbers / next navigation with disabled-state ink for edge pages (Current == 1 disables prev, Current == Total disables next);
EventMouseDownroutes to whichever button rect contains the click.
Development note
Built in parallel by 4 sub-agents in worktree isolation (/tmp/tk-pp-{a,b,c,d}) per the org's parallel-agent-worktree-isolation rule. Each agent worked in its own fresh clone of v0.7.0; the 4 batches merged verbatim with no naming conflicts across the ~30 new package-level constants and enums, and 100.0% cov end-to-end.
Verified
go vet ./...cleango test -cover ./...→ 100.0% of statements- Cross-compile smoke:
js/wasm,linux/arm64,darwin/arm64all clean - CI green
Compatibility
Additive only. No new theme fields, no new external deps beyond github.com/go-widgets/painter. No existing files touched.
To pin:
```go
require github.com/go-widgets/toolkit v0.8.0
```
License
BSD-3-Clause.
v0.7.0 — 9 new widgets, +27% surface
Additive-only bump: 33 → 42 widgets. Every existing consumer keeps working unchanged.
Closes the gap vs GTK 4 (libadwaita) and DaisyUI 4 identified in the 2026-07 audit. All 9 widgets follow the existing conventions: Base embedding, Draw(painter.Painter, *Theme) signature, existing fillRect/strokeRect/DrawText helpers, 100% statement coverage.
Tier 1 — small text-bearing / status widgets
- Switch — compact iOS-style toggle (distinct from
ToggleButton). TrackSurfaceAlt/Accent+Surfaceknob sliding L/R.OnEventflipsOn+ firesOnToggle. - Badge — small pill counter/indicator (
Accentfill,Backgroundink). Auto-sizes whenBounds().W == 0. - Kbd — keyboard-shortcut chip ("⌘K" style).
Surface+Borderstroke +OnSurfacetext. - Alert — persistent semantic banner distinct from
Notification(noLife, noVisible).AlertKindenum (Info/Success/Warning/Error): Info = themeAccent; others fixed RGB tuned for meaning (sea green / amber / brick red).
Tier 2 — structural DaisyUI-style
- Card — three-zone container (Title header + Body multi-line + Footer strip); each zone optional.
Surfacefill +Borderstroke wrap. - Breadcrumbs — horizontal nav path (
OnBackgroundtext +Borderchevron), auto-positioned viaTextWidth. - Steps — horizontal step indicator
[1]—[2]—[3]—[4]with Accent fill up to and includingCurrent,SurfaceAltfor pending. 1-pxBorderconnector between badges. Optional caption below each badge.
Tier 3 — larger structural
- HeaderBar — GTK client-side-decorations pattern:
Title+ optionalSubtitle+Start[]widgets (LTR from left edge) +End[]widgets (RTL from right edge). ConstantsHeaderBarHeight = 40,HeaderBarPad = 8,HeaderBarSubtitleGap = 2. - Table — structured data grid (
Columns/Rows/Selected). Header row + zebra-striped body + optional selection highlight + column separators + auto/fixed column widths +\"(no data)\"placeholder for empty rows. Reuses thetheme.Extra[\"OnAccent\"]fallback pattern fromButton/ListBox/TreeView.
Development note
Built in parallel by 4 sub-agents (worktree-isolated per the org's parallel-agent-worktree-isolation rule). Each agent worked in its own clone from v0.6.2; the 4 batches merged verbatim with no naming conflicts and 100% cov end-to-end — the sub-agent approach scales cleanly when widgets are decoupled by file scope.
Verified
go vet ./...cleango test -cover ./...→ 100.0% of statements across the entire package- CI green in 9s on the first push
Compatibility
Additive only. No new theme fields, no external deps beyond github.com/go-widgets/painter. No existing files touched.
To pin:
```go
require github.com/go-widgets/toolkit v0.7.0
```
What's next
Still missing vs GTK / DaisyUI: Popover, SearchEntry, Avatar, Toast (transient), Banner, ActionRow / PreferencesPage pattern, Skeleton, Diff, ChatBubble, ViewSwitcher, Rating, Pagination. Fair game for a follow-up cycle.
License
BSD-3-Clause.
v0.5.0 — 35 widgets · GTK-theme aware · 100 % coverage
First tagged release of the toolkit under its go-widgets org home.
Highlights
- 35 widgets across Base / Text / Action / Input / Selection / Layout / Tabs / Scroll / Feedback / Navigation / Bars / Composite families (see README table)
LoadGTKTheme(css)— parses libadwaita / GTK3@define-color→Theme, with alias-chain resolution (fixed-point iteration, cycle-safe)- 10 stock icon helpers —
DrawIcon{New,Open,Save,Cut,Copy,Paste,Undo,Redo,Search,Settings}, 1-px pixel-art strokes, scale-fit - Notification widget — auto-dismissing toast driven by
Tick() - IME event kinds on
TextView—EventCompositionStart/Update/Endfor dead-key + CJK candidates MenuBar.HandleShortcut(code)— accelerator dispatcherMenuBar.NameWidth/NameOriginX— exports for hosts that render their own popover under a clicked name- MenuBar auto-sized top-level names — no more clipped "Long Menu Name"
Metrics
- 100.0 % of statements covered by unit tests
- Pure Go, stdlib only, CGO=0
- Builds for
GOOS=js GOARCH=wasm+ every native target Go ships - BSD-3-Clause
Consumers
- gallery — browser wasm live demo hosting the full widget family on a plain canvas. Live at go-widgets.github.io/gallery/.
- svg — one-function package that wraps any toolkit render in a self-contained SVG (base64 PNG inside a scaling envelope).
Use
import "github.com/go-widgets/toolkit"
btn := toolkit.NewButton("Click me", func() { println("clicked!") })
btn.SetBounds(toolkit.Rect{X: 20, Y: 20, W: 120, H: 28})
surface := make([]byte, 4*w*h)
btn.Draw(surface, w, toolkit.DefaultLight())Next
go-widgets/tui — planned terminal-cell backend. v0.5.x point releases will land bug fixes + backward-compat additions; API-breaking changes wait for v0.6.