Here's a feature comparison table of the native cross-platform GUI libraries for Go (web technologies based ones are completely skipped):
| Feature | Fyne | GoGi | Gio | Qt Bindings | andlabs/ui |
|---|---|---|---|---|---|
| Basic Widgets | |||||
| Labels | ✅ | ✅ | ✅ | ✅ | ✅ |
| Buttons | ✅ | ✅ | ✅ | ✅ | ✅ |
| Text Input | ✅ | ✅ | ✅ | ✅ | ✅ |
| Checkbox | ✅ | ✅ | ✅ | ✅ | ✅ |
| Radio Buttons | ✅ | ✅ | ✅ | ✅ | ✅ |
| Progress Bar | ✅ | ✅ | ✅ | ✅ | ✅ |
| Advanced Widgets | |||||
| Tables/Lists | ✅ | ✅ | ✅ | ✅ | ✅ |
| Tree View | ✅ | ✅ | ✅ | ✅ | |
| Tab Container | ✅ | ✅ | ✅ | ✅ | |
| Menu Bar | ✅ | ✅ | ✅ | ✅ | |
| Context Menu | ✅ | ✅ | ✅ | ||
| Graphics | |||||
| Custom Drawing | ✅ | ✅ | ✅ | ✅ | |
| 2D Graphics | ✅ | ✅ | ✅ | ✅ | ❌ |
| 3D Support | ✅ | ✅ | ❌ | ||
| Layouts | |||||
| Grid | ✅ | ✅ | ✅ | ✅ | |
| Box/Flow | ✅ | ✅ | ✅ | ✅ | ✅ |
| Custom Layouts | ✅ | ✅ | ✅ | ✅ | ❌ |
| Platform Features | |||||
| Native Dialogs | ✅ | ✅ | ✅ | ✅ | |
| System Tray | ✅ | ❌ | ✅ | ❌ | |
| Notifications | ✅ | ❌ | ❌ | ✅ | ❌ |
| Other | |||||
| Mobile Support | ✅ | ✅ | ✅ | ❌ | |
| Animation | ✅ | ✅ | ✅ | ✅ | ❌ |
| Theming | ✅ | ✅ | ✅ | ✅ | ❌ |
| Accessibility | ✅ | ✅ | |||
| Binary Size | Small | Medium | Small | Large | Small |
| Native Look & Feel | ❌ | ❌ | ❌ | ✅ | ✅ |
| Development Activity | High | Medium | High | Medium | Low |
Legend:
- ✅ Supported
⚠️ Limited/Basic support- ❌ Not supported or requires significant custom work
Here's a simplified comparison of cross-platform GUI libraries for Go, excluding those based on web technologies (Wails, Lorca, WebView). Each of these libraries takes a different approach to creating GUIs without relying on web technologies, offering varying levels of control, appearance, and complexity.
- Approach: Native Go implementation using OpenGL
- Pros: Lightweight, easy API, good documentation, actively maintained
- Cons: Custom-styled widgets (not native OS look)
- Best for: Simple applications where file size matters
- Approach: Pure Go GUI toolkit
- Pros: Rich widget set, 3D capabilities, built-in GoLang IDE
- Cons: Less mature documentation, steeper learning curve
- Best for: Data visualization and complex applications
- Approach: Immediate mode GUI in Go
- Pros: Good performance, low-level control, mobile support
- Cons: Still maturing, requires more code for common UI elements
- Best for: Performance-critical applications, custom interfaces
- Approach: Bindings for C++ Qt framework (e.g., therecipe/qt)
- Pros: Mature, feature-rich, native look and feel
- Cons: Large dependencies, complex setup
- Best for: Enterprise applications requiring native appearance
- Approach: Lightweight wrapper around platform-native GUI libraries
- Pros: Native look and feel, small API surface
- Cons: Limited feature set, less actively maintained
- Best for: Simple utility applications requiring native appearance
Here's a minimal implementation of a tickable digital clock for each library. Each example showcases the basic patterns for rendering a dynamic UI element and handling updates with each library's specific approach to layout, events, and timers.
package main import ( "time" "fyne.io/fyne/v2/app" "fyne.io/fyne/v2/widget" ) func main() { myApp := app.New() window := myApp.NewWindow("Clock") clock := widget.NewLabel("") go func() { for { timeStr := time.Now().Format("15:04:05") clock.SetText(timeStr) time.Sleep(time.Second) } }() window.SetContent(clock) window.ShowAndRun() }package main import ( "time" "github.com/goki/gi/gi" "github.com/goki/gi/gimain" ) func main() { gimain.Main(func() { mainWindow := gi.NewMainWindow("Clock", "Digital Clock", 800, 600) windowView := mainWindow.WindowView() vp := windowView.AddNewVp(0, "clock-view") clockLabel := gi.AddNewLabel(vp, "clock", "") clockLabel.SetProp("text-align", gi.AlignCenter) clockLabel.SetProp("font-size", "xx-large") go func() { for { timeStr := time.Now().Format("15:04:05") clockLabel.SetText(timeStr) clockLabel.Update() time.Sleep(time.Second) } }() windowView.SetFullReRender() mainWindow.StartEventLoop() }) }package main import ( "image" "log" "time" "gioui.org/app" "gioui.org/font/gofont" "gioui.org/layout" "gioui.org/op" "gioui.org/text" "gioui.org/widget/material" ) func main() { go func() { w := app.NewWindow() th := material.NewTheme(gofont.Collection()) var ops op.Ops currentTime := time.Now().Format("15:04:05") ticker := time.NewTicker(time.Second) defer ticker.Stop() for { select { case e := <-w.Events(): switch e := e.(type) { case app.FrameEvent: gtx := layout.NewContext(&ops, e) layout.Center.Layout(gtx, func(gtx layout.Context) layout.Dimensions { label := material.H2(th, currentTime) label.Alignment = text.Middle return label.Layout(gtx) }) e.Frame(gtx.Ops) case app.DestroyEvent: return } case <-ticker.C: currentTime = time.Now().Format("15:04:05") w.Invalidate() } } }() app.Main() }package main import ( "time" "github.com/therecipe/qt/core" "github.com/therecipe/qt/widgets" ) func main() { app := widgets.NewQApplication(0, nil) window := widgets.NewQMainWindow(nil, 0) window.SetWindowTitle("Digital Clock") window.SetMinimumSize2(250, 150) centralWidget := widgets.NewQWidget(nil, 0) window.SetCentralWidget(centralWidget) layout := widgets.NewQVBoxLayout() centralWidget.SetLayout(layout) clockLabel := widgets.NewQLabel2("", nil, 0) clockLabel.SetAlignment(core.Qt__AlignCenter) font := clockLabel.Font() font.SetPointSize(24) clockLabel.SetFont(font) layout.AddWidget(clockLabel, 0, 0) timer := core.NewQTimer(nil) timer.ConnectTimeout(func() { timeStr := time.Now().Format("15:04:05") clockLabel.SetText(timeStr) }) timer.Start(1000) // 1000ms = 1s window.Show() app.Exec() }package main import ( "time" "github.com/andlabs/ui" ) func main() { err := ui.Main(func() { window := ui.NewWindow("Digital Clock", 200, 100, false) window.SetMargined(true) clockLabel := ui.NewLabel("") box := ui.NewVerticalBox() box.SetPadded(true) box.Append(clockLabel, false) window.SetChild(box) ticker := time.NewTicker(time.Second) go func() { for range ticker.C { ui.QueueMain(func() { clockLabel.SetText(time.Now().Format("15:04:05")) }) } }() window.OnClosing(func(*ui.Window) bool { ui.Quit() return true }) window.Show() }) if err != nil { panic(err) } }