Pure-Go widget toolkit that renders into an RGBA byte buffer. Zero
JS / DOM / canvas dependency — every widget composes pixels into a
caller-supplied []byte, so the toolkit runs identically in
GOOS=js GOARCH=wasm (browser SharedArrayBuffer clients), on any
native target Go ships (native canvas backends, image files), or
in headless tests (screenshot-hash regressions).
- One toolkit per app: consumers stop reinventing buttons,
scrollbars, text fields. They import
github.com/go-widgets/toolkitand compose. - Coherent theming: a single
Themevalue cascades through every widget — change a colour at the top, see it everywhere.LoadGTKTheme(css)parses libadwaita / GTK3@define-colordeclarations into aTheme, so any GTK-desktop palette (Adwaita, Juno, WhiteSur, Solarized …) drives the widget ink colours. - Pure Go + CGO=0: no C toolchain, no shared libraries. Builds
for every Go target including
GOOS=js GOARCH=wasm. - A11y bridge: core widgets implement an opt-in
Accessibleinterface (A11y() A11yInfo) so a host canCollectA11yand publish them to screen readers without rewriting every widget.
- Not a CodeMirror replacement. Complex web-grade editors are best embedded via an iframe overlay at the host level.
- Not GTK. No CSS engine, no SVG renderer, no full BiDi/IME. A broad primitive set — buttons, inputs, containers, feedback, overlays, structural rows, semantic banners, data displays — around 8 kLoC of widget code with double that in tests.
v0.9 — GTK 4 / DaisyUI parity pass. Three additive waves closed the coverage gap versus libadwaita and DaisyUI 4: v0.7 shipped 9 core widgets (Switch, Badge, Kbd, Alert, Card, Breadcrumbs, Steps, HeaderBar, Table), v0.8 shipped 12 overlays and structural rows (Avatar, Skeleton, Rating, Toast, Banner, Popover, ActionRow, ViewSwitcher, ChatBubble, SearchEntry, Diff, Pagination), v0.9 shipped 8 finishing widgets (SplitButton, IconButton, Stat, Timeline, DropZone, Chip, FormField, ProgressCircle).
62 widgets + 10 stock icons, ~8k LoC of widget code, 100% statement
coverage.
Pure Go, no CGO, stdlib only. Builds for GOOS=js GOARCH=wasm and
every native target Go ships.
| Family | Widgets |
|---|---|
| Base | Widget, Base, Rect, Event (+ IME composition), Theme, RGBA |
| Text | bitmap 5x7 font (60+ glyphs), DrawText, TextWidth, Label |
| Action | Button, ToggleButton, CheckButton, RadioButton + Group |
| Input | Entry, TextView + Selection + IME preview, SpinButton, Scale, RangeSlider |
| Selection | ListBox, TreeView, DropDown |
| Layout | HBox, VBox, Grid, Frame, Stack, Overlay, Paned, Expander |
| Tabs | Notebook |
| Scroll | ScrollView |
| Feedback | ProgressBar, LevelBar, Spinner, Image, Tooltip, Notification |
| Navigation | Menu + MenuItem.Shortcut, MenuBar + Alt+letter, ContextMenu, Dialog, MessageDialog |
| Bars | Toolbar, Statusbar, 10 stock DrawIcon* helpers |
| Composite | FileChooser, ColorChooser, FontChooser, Calendar, DatePicker, MarkdownView |
| Charts | LineChart, BarChart, PieChart |
| Theming | LoadGTKTheme(css) (GTK3 + libadwaita @define-color → Theme) |
Widget.Draw signature moved from
Draw(surface []byte, surfaceW int, theme *Theme)to
Draw(p painter.Painter, theme *Theme)where painter.Painter is
a 5-primitive interface (FillRect, StrokeRect, PutPixel,
Text, Size). Existing callers that had a []byte + width
migrate one line:
// before v0.6:
wg.Draw(surface, w, theme)
// v0.6+:
p := painter.NewPixelPainter(surface, w, h)
wg.Draw(p, theme)The []byte is still writable + owned by the caller — the
PixelPainter just wraps it so the primitives translate to writes.
All other widget APIs (Bounds, SetBounds, HitTest, OnEvent,
NewButton, …) are unchanged.
toolkit.Rect + toolkit.RGBA became type aliases of
painter.Rect + painter.RGBA, so cross-package assignments work
transparently.
The 10 DrawIcon* helpers also lost their (surface, surfaceW)
prefix; new signature is DrawIconX(p painter.Painter, r Rect, ink RGBA).
- v0.9 — 8 widgets: SplitButton (button + attached dropdown arrow), IconButton (toolbar-icon variant), Stat (KPI card with trend indicator), Timeline (vertical event log), DropZone (dashed file drop target), Chip (removable tag), FormField (Label + Child + Help/Error), ProgressCircle (approximated circular progress).
- v0.8 — 12 widgets: Avatar, Skeleton (Text/Avatar/Block kinds), Rating, Toast (transient bottom-of-screen), Banner (persistent full-width), Popover (Visible container for a Child), ActionRow (libadwaita Title/Subtitle/Prefix/Suffix), ViewSwitcher (segmented tab picker), ChatBubble (User/Other bubble), SearchEntry (Entry with prefix + clear), Diff (Context/Added/Removed colored lines), Pagination (prev/numbers/next with disabled ink).
- v0.7 — 9 widgets: Switch (iOS-style toggle distinct from ToggleButton), Badge (auto-sizing pill), Kbd (keyboard-shortcut chip), Alert (Info/Success/Warning/Error semantic banner), Card (Title/Body/Footer three-zone), Breadcrumbs (chevron path), Steps (numbered indicator with connector), HeaderBar (Start/Title/ Subtitle/End GTK CSD), Table (Columns/Rows/Selected data grid).
- v0.6 — Painter back-end abstraction. Every widget's
Drawnow takes apainter.Painterinstead of a fixed[]byte+ stride pair, so the same widget code renders into a pixel buffer (WUI browser canvas, GUI native window, image file), a terminal cell grid (TUI), or an SVG stream. - v0.5 — Toolbar / Statusbar / FileChooser / ColorChooser /
Calendar, Selection on TextView,
LoadGTKTheme(css), 10 stock icon helpers. - v0.4 — 34 widgets. Toolbar / Statusbar, FileChooser /
ColorChooser / Calendar, Selection model on TextView,
LoadGTKTheme(css). - v0.3 — 28 widgets. TextView (multi-line editor), Menu/MenuBar, Dialog/MessageDialog, Tooltip, DropDown, TreeView.
- v0.2 — 22 widgets. Layout containers (HBox/VBox/Grid/Frame), scroll (ScrollView/ListBox), input (Entry/Check/Radio/Toggle), structural (Stack/Notebook/Paned/Expander), feedback (ProgressBar/LevelBar/Scale/SpinButton/Image/Spinner), bitmap font.
- v0.1 — scaffolding. Widget interface, Theme value, Button + Label, primitive event dispatch.
Widget coverage is now materially complete versus GTK 4 + DaisyUI 4. The remaining pre-1.0 work is around the edges of the widget model, not the widget catalogue:
Font family plumbing— done (v0.20): aFontinterface (Advance,Height,Draw) + a scalable built-in bitmap font.SetFont(NewBitmapFont(2))doubles all text ("retina"), and every widget re-lays-out because metrics are now read at draw time. Breaking:GlyphHeight/GlyphAdvanceand the metric-derived dimensions (CardHeaderH,DatePickerFieldH,DiffLineH,FormFieldLabelH,TimelineEventH,CardFooterH) are now functions — append()at call sites when upgrading.First-class drag-and-drop event kinds— done (v0.16):EventDragStart/EventDragMove/EventDragLeave/EventDrop, plus aDragSource/DropTargetinterface pair andSplitDropPayload/JoinDropPayloadfor multi-item payloads. DropZone dropped its synthetic-EventCharseam and now drives its hover cue +OnDropfrom the formal lifecycle as aDropTarget.Context menu helper— done (v0.17):ContextMenuwraps aMenuas a right-click popup —Popup(x, y)shows it at the cursor, it auto-sizes to its items, clamps itself inside the surface, and dismisses on outside-click.Overlay layout container— done (v0.18):Overlaystacks z-orderedLayersabove a primaryContentchild, so Popover / Toast / Notification / Tooltip / ContextMenu float without hosts arranging z-order. Events route top-down;Modalmakes a miss swallow (backdrop) instead of falling through.A11y bridge— done (v0.19): an opt-inAccessibleinterface (A11y() A11yInfo— role + name + value) implemented by the core widgets (Button/Label/Entry/CheckButton/RadioButton/ Switch/Scale), plusCollectA11y([]Widget)so a host republishes them to the platform layer (WAI-ARIA on wasm, TTY metadata on tui).
+----------------------+
| Theme | Palette + metrics + font ref
+----------------------+
|
+----------------------+ +-------------------+
| Widget interface | | Event |
| Draw(rgba, theme) |<---| Kind: click/key |
| HitTest(x, y) | | X,Y / Code |
| OnEvent(ev) | +-------------------+
+----------------------+
|
+-------+-------+-------+-------+
| | | |
+--+---+ +--+--+ +-+--+ +-+--+
|Button| |Label| |HBox| | ...|
+------+ +-----+ +----+ +----+
BSD 3-Clause. See LICENSE.