Go TUI Framework
glyph
Batteries-included terminal UI framework for Go.
Declarative composition with a compile-once rendering model. Keyboard-first. Fast.
$ go get github.com/kungfusheep/glyph@latest copied
01
package main import . "github.com/kungfusheep/glyph" func main() { app, _ := NewApp() count := 0 app.SetView( VBox( Text(&count), Text("↑/↓ to count, q to quit"), ), ) app.Handle("up", func() { count++ }) app.Handle("down", func() { count-- }) app.Handle("q", app.Stop) app.Run() }
A glyph app is a tree of plain functions. VBox stacks its children, Text renders a value. Pass a pointer and the display stays in sync. Wire up key handlers, call Run, done.
Once
Build
When you call SetView(), the declarative tree is compiled into a flat array of operations. All reflection, type switches, and allocation happen here. The composable API surface — VBox.Gap(2).Border(...) — is purely a build-time convenience.
Each update
Execute
Rendering walks the compiled ops and dereferences pointers to read current state. No tree rebuilds, no virtual-DOM diffing, no allocation. Just pointer reads into a cell buffer, then a diff-based flush to the terminal.
02
File browser
Split pane with list navigation and live preview. OnSelect loads the file, TextView renders it.
HBox( VBox.Grow(1).Border(BorderRounded)( List(&files).BindVimNav().OnSelect(func(f *string) { data, _ := os.ReadFile(*f) preview = string(data) }), ), VBox.Grow(2).Border(BorderRounded)( TextView(&preview).Grow(1), ), )
Process monitor
Progress bars for system metrics, sortable table for processes. AutoTable infers columns from struct fields.
VBox( HBox.Gap(4)( Text("CPU"), Progress(&cpuPct).Width(30), Text("Mem"), Progress(&memPct).Width(30), ), AutoTable(&procs).Sortable().Scrollable(20).BindVimNav(), )
Deploy log
Spinner, status text, and progress in a single row. Log streams output live. Result appears conditionally when done.
VBox.Border(BorderRounded).Title("deploy")( HBox.Gap(2)( Spinner(&frame).FG(Cyan), Text(&status).Bold(), Progress(&pct).Width(20), ), Log(output).Grow(1).MaxLines(500), If(&done).Then(Text(&result).Bold().FG(Green)), )
Fuzzy finder
FilterList handles the search input and matching. Custom Render for each row, border and title for framing.
FilterList(&packages, func(p *Pkg) string { return p.Name }). Render(func(p *Pkg) any { return HBox.Gap(2)( Text(&p.Name).Bold(), Text(&p.Desc).FG(BrightBlack), ) }).MaxVisible(15).Border(BorderRounded).Title("packages")
Live dashboard
Two sparkline panels side by side with a scrolling event log below. Update the slices, the view follows.
VBox( HBox.Gap(1)( VBox.Grow(1).Border(BorderRounded).Title("requests/s")( Sparkline(&reqData).FG(Green), Text(&reqRate).FG(BrightBlack), ), VBox.Grow(1).Border(BorderRounded).Title("p99 latency")( Sparkline(&latData).FG(Yellow), Text(&p99).FG(BrightBlack), ), ), Log(events).Grow(1).MaxLines(200), )
Registration form
Form auto-aligns labels, manages focus, and wires validation. Errors surface on blur or submit, as configured.
Form.LabelBold().OnSubmit(register)( Field("Name", Input(&name).Validate(VRequired, VOnBlur)), Field("Email", Input(&email).Validate(VEmail, VOnBlur)), Field("Role", Radio(&role, "Admin", "User", "Guest")), Field("Terms", Checkbox(&agree, "I accept").Validate(VTrue, VOnSubmit)), )
04
$ go get github.com/kungfusheep/glyph@latest copied