Skip to content

Instantly share code, notes, and snippets.

@source-c
Created March 23, 2025 11:21
Show Gist options
  • Select an option

  • Save source-c/8cee2e20eff3de1f97da62790ef876fd to your computer and use it in GitHub Desktop.

Select an option

Save source-c/8cee2e20eff3de1f97da62790ef876fd to your computer and use it in GitHub Desktop.
Golang native GUI libs unified

Comparison Table of Go GUI Libraries

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 ⚠️ Manual
Tab Container ⚠️ Manual
Menu Bar ⚠️ Manual
Context Menu ⚠️ Manual ⚠️ Limited
Graphics
Custom Drawing ⚠️ Limited
2D Graphics
3D Support ⚠️ Limited ⚠️ Limited
Layouts
Grid ⚠️ Basic
Box/Flow
Custom Layouts
Platform Features
Native Dialogs ⚠️ Limited
System Tray ⚠️ Limited
Notifications
Other
Mobile Support ⚠️ Experimental
Animation
Theming
Accessibility ⚠️ Limited ⚠️ Limited ⚠️ Basic
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

Pros & Cons

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.

Fyne

  • 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

GoGi

  • 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

Gio

  • 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

Qt binding (various)

  • 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

ui (andlabs/ui)

  • 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

Samples

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.

Fyne

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() }

GoGi

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() }) }

Gio

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() }

Qt (therecipe/qt)

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() }

UI (andlabs/ui)

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) } }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment