I spent the last few months building Gova. It is a declarative GUI framework for Go that compiles native desktop apps to a single static binary on macOS, Windows, and Linux. Struct-based components, reactive state, real native dialogs where the platform offers them. No Electron, no embedded webview, no JavaScript runtime.
Here is a full Counter app:
package main
import g "github.com/nv404/gova"
type Counter struct{}
func (Counter) Body(s *g.Scope) g.View {
count := g.State(s, 0)
return g.VStack(
g.Text(count.Format("Count: %d")).Font(g.Title),
g.HStack(
g.Button("-", func() { count.Set(count.Get() - 1) }),
g.Button("+", func() { count.Set(count.Get() + 1) }),
).Spacing(g.SpaceMD),
).Padding(g.SpaceLG)
}
func main() {
g.Run("Counter", g.Define(func(s *g.Scope) g.View {
return Counter{}
}))
}
go run . and a real native window appears. No scaffolding, no config file, no build step beyond the Go toolchain.
What ships today
- Components as plain Go structs with typed prop fields, composed with function calls.
- Reactive primitives:
State,Signal,Store,PersistedState. All keyed by call site, which means no hook rules and no string keys. - Real native dialogs on macOS through cgo:
NSAlert,NSOpenPanel,NSSavePanel,NSDockTilebadge, progress, and menu. Fyne fallbacks on Windows and Linux so portable code keeps running. -
gova devCLI with hot reload. UI state optionally survives the reload viaPersistedState. - One codebase, three targets. 32 MB static binary for Counter, 23 MB stripped.
- Headless testing via
TestRenderso you can assert on the view tree without opening a window.
A slightly bigger slice
A todo row with a reactive model, a delete button, and a text field that grows to fill the width:
gova.List(todos,
func(t Todo) int { return t.ID },
func(i int, todo Todo) gova.View {
return gova.HStack(
gova.Toggle(todo.Done).OnChange(func(done bool) {
model.Update(func(m Model) Model {
return toggleTodo(m, todo.ID, done)
})
}),
gova.Text(todo.Text),
gova.Spacer(),
gova.Button("Delete", func() {
model.Update(func(m Model) Model {
return removeTodo(m, todo.ID)
})
}).Color(gova.Red),
)
},
)
Modifier order does not matter. Defaults are Go zero values. The compiler checks your UI.
Install
go get github.com/nv404/gova@latest
Optional CLI for a hot-reload workflow:
go install github.com/nv404/gova/cmd/gova@latest
gova dev ./examples/counter
Requires Go 1.26+ and a C toolchain. One go get pulls Fyne and its native dependencies transitively.
Why another Go GUI framework
Go already has Fyne, Wails, and Gio. I used all three and wanted a different mental model: struct-based components with typed props, reactive state that survives refactors, and real platform widgets where the platform cares about them. Gova sits on top of Fyne for rendering so I did not have to rewrite a toolkit, but the public surface is its own.
Where to look next
- Repo: github.com/NV404/gova
- Docs and examples: gova.dev
- Runnable examples in the repo:
counter,todo,fancytodo,notes,themed,components,dialogs
It is pre-1.0. The API will move before v1.0.0, and I would rather hear honest critique than polite silence.
If the project looks useful, a star on the repo helps it surface to other Go devs looking in the desktop space.
Top comments (0)