Skip to content
Go TUI Framework
glyph
Batteries-included terminal UI framework for Go.
Declarative composition. Your data structures. Keyboard-first. Fast.
$ go get github.com/kungfusheep/glyph@latest copied
01
package main import . "github.com/kungfusheep/glyph" func main() { count := 0 app := NewInlineApp() app.SetView( VBox( Text(&count), Text("↑/↓ to count, enter to quit"), ), ). Handle("<Up>", func() { count++ }). Handle("<Down>", func() { count-- }). Handle("<Enter>", app.Stop). ClearOnExit(true). Run() println("You selected", count) }
A glyph app is a tree of plain functions. VBox stacks its children, Text renders a value. Pass a pointer; glyph reads the current value every update. Keyboard handlers take plain function callbacks. Run starts the event loop and blocks until the app exits.
Follow the full guide →
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. Pointer reads into a cell buffer, then a diff-based flush to the terminal. The execute path is zero-alloc by design.
02

Components are themable and stylable, but they work out of the box with zero configuration.

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.Gap(1)( 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 }). Placeholder("search packages..."). 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; glyph reads the new values on the next update.
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
The values guiding glyph.
Performance is a feature.
Views are a story, not an investigation.
Time-to-understand is a design goal.
The common case should already be solved.
Make the right thing the obvious thing.
Complexity belongs to the framework, not the builder.
05
$ go get github.com/kungfusheep/glyph@latest copied