DEV Community

Cover image for Building desktop WebView apps in Go without CGo
Cesar Gimenes
Cesar Gimenes

Posted on

Building desktop WebView apps in Go without CGo

I have been working on Glaze, a small desktop WebView toolkit for Go.

The short version: Glaze lets a Go program open a native desktop window backed by the WebView already available on the operating system, without using CGo.

It currently targets:

  • macOS, through WKWebView
  • Linux, through WebKitGTK
  • Windows, through WebView2

The project is still young, but the core idea is already useful: keep small Go desktop tools close to the normal Go workflow.

No C compiler in the build path.
No bundled native helper library.
No large application framework around it.

Just Go code calling the system WebView.

Why I wanted this

I write a lot of small tools in Go.

Some of them are fine as CLI programs. Others need a basic interface: a form, a preview, a local dashboard, a small editor, or a way to inspect and manipulate data visually.

For those cases, HTML is often enough. The browser gives me layout, text rendering, forms, tables, keyboard handling, and a familiar debugging model.

But I do not always want to ship a web server as the user interface. I also do not always want to pull in a large desktop framework when all I need is a native window around a local UI.

A WebView is a reasonable middle ground.

The problem is that many WebView solutions eventually bring CGo, native build tooling, helper libraries, or larger framework assumptions into the project.

That is not necessarily wrong. For many applications, those trade-offs are acceptable.

For this project, I wanted something narrower.

The design constraint

The main constraint behind Glaze is simple:

Use the WebView already provided by the OS, but call it from Go without CGo.

Glaze uses purego to call native platform APIs directly from Go.

That means each backend talks to the platform WebView:

  • WKWebView on macOS
  • WebKitGTK on Linux
  • WebView2 on Windows

The result is not a full GUI toolkit. That is intentional.

Glaze is focused on the window, the WebView, JavaScript-to-Go bindings, and a few desktop helpers that are useful for small tools.

Hello world

A minimal Glaze program looks like this:

package main

import (
    "log"

    "github.com/crgimenes/glaze"
)

func main() {
    w, err := glaze.New(true)
    if err != nil {
        log.Fatal(err)
    }
    defer w.Destroy()

    w.SetTitle("Glaze")
    w.SetSize(800, 600, glaze.HintNone)
    w.SetHtml("<h1>Hello from Glaze</h1>")

    w.Run()
}
Enter fullscreen mode Exit fullscreen mode

That opens a native window and renders HTML inside the system WebView.

A local Go app in a desktop window

One of the patterns I care about most is turning an existing net/http application into a desktop app with minimal changes.

Glaze has an AppWindow helper for that.

package main

import (
    "fmt"
    "log"
    "net/http"

    "github.com/crgimenes/glaze"
)

func main() {
    mux := http.NewServeMux()

    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "text/html; charset=utf-8")

        _, err := fmt.Fprint(w, `<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Glaze App</title>
    <style>
        body {
            font-family: system-ui, sans-serif;
            margin: 2rem;
        }
        button {
            font: inherit;
            padding: 0.5rem 1rem;
        }
    </style>
</head>
<body>
    <h1>Local Go UI</h1>
    <p>This page is served by a Go http.Handler and displayed in a desktop WebView.</p>
    <button onclick="location.reload()">Reload</button>
</body>
</html>`)
        if err != nil {
            log.Printf("write response: %v", err)
        }
    })

    err := glaze.AppWindow(glaze.AppOptions{
        Title:   "Glaze App",
        Width:   1024,
        Height:  700,
        Handler: mux,
    })
    if err != nil {
        log.Fatal(err)
    }
}
Enter fullscreen mode Exit fullscreen mode

This is the style of application I had in mind: local tools, internal utilities, small editors, dashboards, inspectors, and experiments.

The UI can still be plain HTML, CSS, and JavaScript. The backend can still be normal Go.

Calling Go from JavaScript

Glaze also supports binding Go functions to JavaScript.

That makes it possible to keep the UI lightweight while still doing real work in Go.

The goal is not to turn every application into a complex frontend. The useful case is exposing a small surface area: save a file, query local state, run a calculation, trigger an operation, or send an event.

This fits well with small tools where most of the logic belongs in Go and the WebView is mainly the presentation layer.

What Glaze is not trying to be

Glaze is not trying to replace mature GUI toolkits.

It is also not trying to replace larger frameworks for building full desktop applications.

Those projects usually solve broader problems: packaging, application lifecycle, icons, installers, auto-update, system integration, custom controls, and more.

Glaze is intentionally smaller.

The target use case is closer to:

  • “I have a Go tool and want a small local UI”
  • “I already have an HTTP handler and want to show it in a desktop window”
  • “I want HTML for layout but do not want to ship a browser”
  • “I want to avoid CGo in this project”
  • “I want a thin WebView layer instead of a full application framework”

That scope keeps the API easier to reason about.

Platform notes

macOS is the cleanest case because the Cocoa and WebKit frameworks are already part of the system.

Windows depends on the Microsoft Edge WebView2 Runtime, which is already present on current Windows installations in many cases.

Linux is the hard case. Different distributions package WebKitGTK differently, and dynamic library names matter. Glaze tries to detect GTK4/WebKitGTK first and falls back to GTK3/WebKitGTK where appropriate, but Linux still needs more testing across distributions.

That is one of the areas where feedback would be useful.

Current features

At the moment Glaze includes:

  • desktop WebView windows
  • JavaScript-to-Go binding
  • BindMethods helper for exposing exported Go methods
  • RenderHTML helper for Go templates
  • AppWindow for local net/http apps
  • Go-to-JavaScript event bridge
  • native file dialogs
  • native menu support on macOS and Windows
  • runnable visual examples

The repository also includes small demos such as Game of Life, Starfield, Doom Fire, Mandelbrot, Falling Sand, Raycasting, and a Filo REPL.

GitHub logo crgimenes / glaze

Glaze: a CGo-free desktop WebView toolkit for Go

Glaze

Glaze is a desktop WebView binding for Go. It is a pure-Go port of webview/webview built on purego, keeping CGo out of the picture. Each backend talks to the WebView framework the OS already ships -- WKWebView on macOS, WebKitGTK on Linux, WebView2 on Windows -- so nothing native is bundled.

It started as a fork of go-webview but has diverged enough to live as a separate codebase with its own goals and API.

Examples
















Desktop Game of Life Starfield
Desktop example preview Game of Life example preview Starfield example preview
















Doom Fire Mandelbrot Falling Sand
Doom Fire example preview Mandelbrot example preview Falling Sand example preview













Raycasting Filo REPL
Raycasting example preview Filo REPL example preview

Why no CGo

This is the whole point of the project, and it's the part that's easy to miss. Most native-WebView bindings reach for CGo, which quietly takes back the things that make Go pleasant to ship: cross-compiling suddenly needs a matching C cross-compiler for every target (mingw for Windows, a sysroot for Linux), builds stop being reproducible, and go




Trade-offs

The main benefit is keeping the project close to regular Go development.

The main cost is that Glaze sits close to native platform APIs, so platform differences are real.

Some trade-offs are deliberate:

  • It does not bundle a browser.
  • It does not try to hide every OS difference.
  • It does not try to become a complete desktop framework.
  • Linux support depends on system WebKitGTK libraries.
  • Windows support depends on WebView2 runtime behavior.

For my use case, that is acceptable. I prefer a small, explicit layer over a large abstraction that hides too much.

Where I want to take it

The immediate focus is stability and examples.

The most valuable improvements right now are:

  • testing on more Linux distributions
  • better diagnostics when system WebView libraries are missing
  • more examples showing realistic local tools
  • API feedback from Go developers who build small desktop utilities
  • clearer documentation for platform-specific behavior

I am also interested in keeping the project boring in the good sense: small API, predictable behavior, explicit errors, and no unnecessary magic.

Repository

The project is here:

https://github.com/crgimenes/glaze

Feedback is welcome, especially around the API shape, Linux/WebKitGTK behavior, and whether the examples make the intended use case clear.

Top comments (1)

Collapse
 
hari_haran_144973263df174 profile image
Hari Haran

Avoiding CGo changes the whole deployment story. It would be interesting to see how this handles platform differences and native WebView features.