Thanks to visit codestin.com
Credit goes to gist.github.com

Skip to content

Instantly share code, notes, and snippets.

@source-c
Created March 23, 2025 11:21
Show Gist options
  • Save source-c/8cee2e20eff3de1f97da62790ef876fd to your computer and use it in GitHub Desktop.
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