Flatte is a Go TUI foundation built around one mutable state struct, direct
state mutation, and pure views. It is intentionally not a Bubble Tea clone:
apps do not define messages, commands, or component update trees. State lives
in your app, Handle mutates it, and View renders it.
A glimpse of the sample apps — Snake, a Docker-style TUI,
a three-pane workspace, styling, tables, progress — all built from the
primitives below. Try Flatte live in your browser →
· regenerate the reel with cmd/record-demo.sh.
The module path is currently:
go get github.com/lunguini/flatteThe root package name is flatte, so ordinary imports use the flatte
identifier:
import "github.com/lunguini/flatte"package main
import (
"context"
"fmt"
"os"
"github.com/lunguini/flatte"
)
type State struct {
count int
}
func Handle(s *State, ev flatte.Event, fx flatte.Effects[State]) {
key, ok := ev.(flatte.KeyEvent)
if !ok {
return
}
switch key.Key {
case flatte.KeyEscape:
fx.Quit()
case flatte.KeyCharacter:
switch key.Rune {
case '+':
s.count++
case '-':
s.count--
case 'q', 'Q':
fx.Quit()
}
}
}
func View(s *State, ctx flatte.RenderContext) flatte.Frame {
return flatte.Frame{Content: fmt.Sprintf("count: %d\n\n+/- change q quit", s.count)}
}
func main() {
if err := flatte.Run(context.Background(), flatte.App[State]{
State: &State{},
Handle: Handle,
View: View,
}); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}Stateis the single source of truth.Handle(state, event, effects)mutates state directly.View(state, context)returns a fullflatte.Frame.- Async work goes through named helpers:
flatte.Go,flatte.Every,flatte.Stream, andflatte.Latest. - Widgets in
flatuiare opt-in state structs. They own no goroutines and no key policy. - Tests use normal field assertions plus
flatestgolden/harness helpers.
github.com/lunguini/flatte(flatte) - runtime, events, effects, frame, cursor, clipboard, exec, file selection.github.com/lunguini/flatte/flatui- stateful UI helpers such asTextField,Textarea,Viewport,List,Table,Tree,FocusRing,Paginator,Progress,Spinner,Timer, andStopwatch.github.com/lunguini/flatte/flatui/layout- flexbox-style layout engine (Row,Col,Text,Spacer) that solves a frame tree once into composed content plus per-ID rects for geometry-based hit-testing.github.com/lunguini/flatte/flatest- deterministic app driver, golden assertions, frame rendering, and replay helpers.
Use plain state:
- Multiple full screens: store a
screenenum inStateand switch inHandleandView. Seecmd/flat-pages. - Multiple focusable sections on one screen: store a
flatui.FocusRing. Seecmd/flat-workspace. - Modal/overlay state: store
modalOpen booland modal-specific fields. Seecmd/flat-modal.
The library and samples are separate modules:
go test ./...
go vet ./...
cd cmd
go test ./...
go run ./flat-pages
go run ./flat-workspaceFlatte stands on a lot of excellent open-source work:
- Charm — Flatte is the deliberate inverse of
Bubble Tea, but it happily
reuses Charm's MIT-licensed substrate:
Lip Gloss for styling,
Ultraviolet for input parsing
and cell-buffer rendering, and
x/ansiandcolorprofileunderneath. Bubble Tea also serves as our honest benchmark — thecmd/bubble-*comparison apps keep our claims grounded. Thank you, Charm team. - rivo/uniseg for correct Unicode grapheme segmentation, so cursors and widths behave.
- The Go team for the language,
golang.org/x/term, and first-class WebAssembly support that powers the browser demo incmd/flat-landing.
- Quick Reference maps common TUI needs to Flatte APIs.
- Examples maps every sample app to the capability it demonstrates.
- Contributing covers Conventional Commits, the quality bar, and the golden-snapshot policy.
- Changelog is generated from commit messages on each release.
