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, dashboard/data widgets, charts and desktop-shell pieces — around 30 kLoC of widget code with roughly 40 kLoC of tests.
v0.109.0 — broad GTK 4 / DaisyUI / desktop-shell coverage. The
widget set has grown well past the early parity passes into a full
application toolkit: ~150 widget types across inputs, containers,
overlays, structural rows, semantic banners, a dashboard/data suite,
a chart family, desktop-shell pieces (status area, wallpaper,
command palette), and a loading-screen Skeleton family. A
declarative Container + swappable Layout model (FitLayout,
BoxLayout, BorderLayout, CardLayout, FlowLayout) sits under
the convenience containers (HBox/VBox/Grid/Frame/Dock/
Border).
~150 widget types + 10 stock icons, ~30 kLoC 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, Accessible/A11yInfo, Focusable, Measurer |
| Text | bitmap 5x7 + anti-aliased NewTrueTypeFont, one-call AA default UseOpenTypeText, global SetFont + per-widget Base.Font, DrawText, TextWidth, Label |
| Action | Button, ToggleButton, CheckButton, RadioButton + RadioGroup, Switch, SplitButton, IconButton, CycleButton, Chip, SegmentedBar |
| Input | Entry, TextView + Selection + IME preview + optional Highlighter (syntax spans) + ShowLineNumbers gutter, SpinButton, Scale, RangeSlider, SearchEntry, TagField, ComboBox, FormField, Validate/Rule |
| Terminal | TerminalView (Cols×Rows TermCell grid, block cursor, wrap/scroll, per-cell FG/BG) |
| Selection | ListBox, TreeView, DropDown |
| Containers | Container + swappable Layout (FitLayout, BoxLayout, BorderLayout, CardLayout, FlowLayout); convenience wrappers HBox, VBox, Grid, Frame, Dock, Border, Stack, Overlay, Paned, Expander, Accordion; declarative Node builder + ViewController (LookupAs) |
| Tabs | Notebook, ViewSwitcher, Carousel |
| Scroll | ScrollView, Scrollbar |
| Feedback | ProgressBar, ProgressCircle, LevelBar, Spinner, Image, Tooltip, Notification, Toast, Banner, Alert, Badge, LoadMask, Backdrop, FocusRing |
| Loading | Skeleton (Text/Rect/Circle/Avatar/Block kinds, shimmer via SetPhase), SkeletonGroup, NewSkeletonCard, NewPageSkeleton |
| Structure | Card, HeaderBar, ActionRow, Breadcrumbs, Steps, Avatar, Rating, Stat, Kbd, ChatBubble, Diff, Pagination, DropZone |
| Navigation | Menu + MenuItem.Shortcut, MenuBar + Alt+letter, ContextMenu, Popover, CommandPalette, Dialog, MessageDialog, Wizard |
| Window | Window, WindowDecoration, DecoButton (client-side decorations) |
| Bars | Toolbar, PagingToolbar, Statusbar, 10 stock DrawIcon* helpers |
| Composite | FileChooser, ColorChooser, ColorPicker, FontChooser, Calendar, DatePicker, DateRangePicker, TimePicker, MarkdownView, MarkdownEditor |
| Data suite | Table (cell edit, frozen columns, group rows, aggregates, row-expanders), TreeTable, PropertyGrid, Kanban (drag cards), Gantt (drag/resize bars), Agenda/AgendaCalendar event calendar (week/month/quarter/year, colour-coded calendars, sidebar, inline editor) |
| Charts | LineChart, BarChart, PieChart, AreaChart, ScatterChart, RadarChart, Gauge, Sparkline |
| Shell | StatusIcon, StatusArea, Wallpaper, Thumbnail |
| Motion | Easing/Tween (Linear, EaseIn*/Out*/InOut*), GestureRecognizer |
| 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).
- Skeleton (loading placeholders) — shape kinds Text (rounded
bars, tunable LineH/LineGap/LastFrac/Radius), Rect (rounded media
block), Circle (avatar), plus the original Avatar/Block swap-parity
kinds. Optional diagonal shimmer band (a lighter tint over
Theme.SurfaceAlt, working in light + dark) driven by
SetPhase(t)advanced per frame.SkeletonGroupcomposes primitives; presetsNewSkeletonCard(bounds)(avatar + 2-line header + media block) andNewPageSkeleton(bounds)(top bar + paragraph groups + image blocks, the webengine browser client's loading screen). - 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. Extended since with anti-aliased vector text (NewTrueTypeFont(ttf, px)) and, from v0.34, a per-widget font: setwidget.Font(the exportedBase.Fontfield) to give one widget its own face/size — e.g. a 10px badge next to a 22px title — while every other widget keeps the globalSetFontfont. A widget with noFontset (the default,nil) renders exactly as before. Since v0.77,UseOpenTypeText()turns on anti-aliased, shaped text for the whole UI in one call — it installs the bundled Atkinson Hyperlegible face (fromgithub.com/go-opentype/fonts, no extra imports,js/wasm-safe) as the active font, so consumers get crisp AA/multi-script glyphs without sourcing a face of their own. UseUseOpenTypeTextSize(px)for a specific size, orDefaultOpenTypeFont(px)to compose the default face into aNewFallbackFontchain (add a CJK/Arabic face) beforeSetFont. The 5x7 bitmap stays the compiled-in default (so existing pixel/metric tests keep their geometry); AA is an explicit opt-in, andSetFont(nil)restores the bitmap.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.