████████╗ ██████╗ ██████╗ ███████╗██╗ ██╗
╚══██╔══╝██╔═══██╗██╔═══██╗██╔════╝╚██╗ ██╔╝
██║ ██║ ██║██║ ██║█████╗ ╚████╔╝
██║ ██║ ██║██║ ██║██╔══╝ ╚██╔╝
██║ ╚██████╔╝╚██████╔╝███████╗ ██║
╚═╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚═╝Build declarative terminal UIs in Go
Compose node trees. Tooey handles layout, diffing, and rendering — you just describe what the screen should look like.
go get github.com/stukennedy/tooeySee it in action
Everything you need for terminal UIs
A batteries-included framework with an Elm-inspired architecture, high-performance rendering, and declarative composition.
Elm Architecture
Init, Update, View. Pure functions, message-driven updates, immutable state. Predictable and testable by design.
30fps Diffing
Efficient frame diffing renders only changed cells as minimal ANSI sequences. Smooth, flicker-free output.
Declarative Nodes
Text, Box, Row, Column, List, Pane, Spacer — compose UIs with value structs and method chaining.
Async Commands
Return commands from Update that run in goroutines and deliver messages back to the update loop.
Focus Management
Declarative focus with Tab/Shift-Tab cycling and escape-to-pop context stack navigation.
Server-Driven UI
Built-in SSE client for server-sent events with HTTP action posting for real-time updates.
The rendering pipeline
From keypress to screen update in one clean pipeline. Each stage is independent and composable.
Virtual node tree → single-pass flex layout → row-major cell buffer → frame diff → minimal ANSI output
Simple, expressive, Go-native
Build complete terminal applications with just a few functions. No boilerplate, no ceremony.
package main
import (
"context"
"github.com/stukennedy/tooey/app"
"github.com/stukennedy/tooey/node"
)
type model struct {
count int
}
func main() {
a := &app.App{
Init: func() interface{} {
return &model{}
},
Update: func(m interface{}, msg app.Msg) app.UpdateResult {
mdl := m.(*model)
switch msg.(type) {
case app.KeyMsg:
if msg.(app.KeyMsg).Key == "q" {
return app.UpdateResult{Model: nil} // quit
}
mdl.count++
}
return app.UpdateResult{Model: mdl}
},
View: func(m interface{}, focused string) node.Node {
mdl := m.(*model)
return node.Column(
node.Text("Press any key to increment, q to quit"),
node.Text(fmt.Sprintf("Count: %d", mdl.count)),
)
},
}
a.Run(context.Background())
}// Compose UI with value structs and chaining
node.Column(
node.Box(node.BorderRounded,
node.Text("Hello Tooey").WithFocusable(),
),
node.Row(
node.Text("Left"),
node.Spacer(),
node.Text("Right"),
),
node.List(items...).WithFlex(1).WithScrollToBottom(),
)Up and running in seconds
One dependency. Standard Go tooling. No CGo, no build scripts.
Install
go get github.com/stukennedy/tooeyRequirements
Go 1.24+ and a terminal supporting ANSI escape sequences. Only external dependency: golang.org/x/term
Run the Demos
go run ./demos/list # Interactive selection list
go run ./demos/maude # Chat TUI with markdown