How to Build Beautiful GUIs in Golang : 3 Web UI Paths
Web UIs often look like magic compared to native apps.
I used to think the web had some mythical language behind it. Turns out?
It’s a C++ engine example Chrome, running CSS, JavaScript, and HTML. The elegance doesn’t come from the syntax, but from the ecosystem. Web dev moves fast because the market demands it.
Here’s the fun part: almost every low-level language can hook into C or C++. Golang is no different.
You don’t need a miracle to build beautiful GUIs in Go. Just wire up a lightweight webview and let Go in the backend do what it does best: be fast and powerful.
Here are 3 battle-tested ways to do it:
- Golang Wails – A production-ready webview stack with frontend templates baked in.
- Web UI – Spin up the user’s existing browser and inject your HTML/CSS/JS directly.
- Raw Webview – Like Wails, but with full control and flexibility.
With these tools, you can build gorgeous, fast desktop utility apps, all powered by Golang.
-
1. Wails: Full Web UI in Go with WebView
- Windows:
- macOS:
- Linux:
- Embedding Frontend Assets
- Binding Go Functions to the Frontend
- Build for Production
-
2. WebUI: Use the User’s Default Browser
- Setup
- Project Structure
- Sample
main.go
-
3. Webview: Embedded Simplicity, Full Control
- Setup
main.go
- Asset Loading
- So Which One?
- Wrapping Up
1. Wails: Full Web UI in Go with WebView
go mod init github.com/wailsexample
Go to wails.io and follow the platform-specific setup. TL;DR:
Windows:
Install WebView2.
macOS:
xcode-select --install
Linux:
Make sure you have:
gcc
libgtk-3
libwebkit2gtk
Then install the Wails CLI:
go install github.com/wailsapp/wails/v2/cmd/wails@latest
Check your setup:
wails doctor
Wails includes frontend templates:
- Svelte
- React
- Vue
- Preact
- Lit
- Vanilla
We’ll go with Vanilla JS, simple and fast:
wails init -n wailsapp -t vanilla
Key project files:
/main.go → your Go backend
/frontend/ → your frontend (JS, HTML, CSS)
/wails.json → config
Start the app:
wails dev
Wails handles everything under the hood.
Embedding Frontend Assets
In main.go this line:
//go:embed all:frontend/dist
var assets embed.FS
This loads your built frontend files. You can change the directory to point to any project with an index.html
.
Binding Go Functions to the Frontend
In main.go
, define and bind a function to the App
struct:
func (a *App) Hello(name string) string {
return fmt.Sprintf("Hello %s!", name)
}
Wails will expose it to the frontend.
In JS:
import { Hello } from "../wailsjs/go/main/App"
Hello("Sk").then(alert).catch(console.error)
All bound functions are async.
More details in the Wails dev guide.
Build for Production
wails build
That’s it. I used Wails in production at a company, it Just. Works.
2. WebUI: Use the User’s Default Browser
WebUI skips WebView entirely and launches the user’s installed browser. Lightweight and clever, but a bit limited. Some methods haven’t been ported to Go yet.
Use it if:
- You want minimal setup.
- You don’t need to reuse windows in the same process(weird memory leak).
Setup
go mod init github.com/webuiexample
Then run:
Windows:
Invoke-WebRequest https://raw.githubusercontent.com/webui-dev/go-webui/main/setup.bat -OutFile setup.bat
./setup.bat
macOS/Linux:
sh -c "$(curl -fsSL https://raw.githubusercontent.com/webui-dev/go-webui/main/setup.sh)"
./setup.sh
Project Structure
/ui/index.html
main.go
Sample main.go
package main
import (
"time"
ui "github.com/webui-dev/go-webui/v2"
)
const w = ui.Window(1)
func exit(e ui.Event) any {
ui.Exit()
return nil
}
func main() {
ui.SetDefaultRootFolder("ui")
w.NewWindow()
w.ShowBrowser("index.html", ui.ChromiumBased)
w.Bind("exit", exit) // exposes function to the frontend
go getLocalStoreContents()
ui.Wait()
}
func getLocalStoreContents() {
time.Sleep(5 * time.Second)
content, _ := w.Script(`return localStorage.getItem("content");`, ui.ScriptOptions{})
println(content)
}
In your frontend (index.html
):
<script src="webui.js"></script>
<script>
localStorage.setItem("content", "Hello World!"); // getLocalStoreContents grabs this
setTimeout(() => {
webui.exit(); // will call exit after 5 seconds
}, 5000);
</script>
getLocalStoreContents
injects a runs a script in the web, in our case fetching contents in the local storage:
content, _ := w.Script(`return localStorage.getItem("content");`, ui.ScriptOptions{})
You can also call JS functions from Go:
e.Window.Run(`SetCount(10);`)
And define it in JS:
function SetCount(value) {
console.log("Count is", value)
}
Pretty straightforward.
3. Webview: Embedded Simplicity, Full Control
This one’s my favorite.
It’s like WebUI, but embedded like Wails. Lightweight, flexible, and you control the browser.
Make sure WebView is installed (if you followed the Wails setup, you’re good).
Setup
go mod init github.com/webviewexample
go get github.com/webview/webview_go
main.go
package main
import (
"fmt"
webview "github.com/webview/webview_go"
)
func main() {
w := webview.New(true) // create the webview
defer w.Destroy()
w.SetTitle("Webview")
w.SetSize(600, 420, webview.HintNone)
w.Bind("hello", func() string { // same as webui exposing the function
return "Hello from Webview!"
})
w.Navigate("C:/path/to/index.html") // loading the frontend
w.Run()
}
In your HTML:
<script>
setTimeout(() => {
window.hello().then(alert)
}, 3000);
</script>
Just like WebUI, but with more control.
Asset Loading
Paths can be a nightmare in any language, because of the difference between systems.
And how go handle binary when using go run .
the binary is place in a random generated(UUID like) folder. so assets are hard to find.
So You’ll want to either:
- Use
embed.FS
- Or spin up a tiny server mux
I’ve got a mini continuation article on Coffee & Kernels where I walk you through both approaches, perfect for React or more complex apps.
So Which One?
- Use Wails if you want to ship a polished app fast.
- Use Webview if you want low-level control and stability.
- Use WebUI if you want something quick and clever, but don’t need much flexibility.
Wrapping Up
We explored 3 ways to build GUI apps with Golang:
- Wails : Full framework for production-ready apps
- WebUI : Lightweight, browser-based UIs
- Webview : Embedded, low-level control
All 3 do the same thing, serve a frontend with Go behind the scenes. Which one you pick depends on your use case.
Know any elegant or underrated ways to build low-level UIs I didn’t mention? Or built something with one of these approaches? Drop it in the comments; I’d love to see what you’ve made.
And hey, once you realize everything eventually boils down to C, C++, and ultimately assembly. you start to see that you can inject anything into everything.
I’ll be posting more deep dives on backend topics, Golang, and low-level systems on Substack. Would love to have you there; come say hi: