Overview
A terminal UI framework for Go built on the Elm Architecture (Model / Update /
View). It uses only the standard library: go.mod has no require directive,
and a test in internal/archtest fails if any package imports outside the
standard library and this module.
An application implements tui.Model, hands it to tui.NewProgram with the
options it wants, and calls Run. The Program reads keys, mouse events and
resizes, calls Update on each Msg, runs the Cmds Update returns, and redraws
View with a cell diff so only changed cells are written.
Guides, a cookbook and screens of every example are at tui.nizaami.com; the API reference is on pkg.go.dev.
Install
Section titled “Install”Requires Go 1.25 or later.
$ go get github.com/ows4444/tuiThere is no tagged release yet, so this resolves to a pseudo-version of the latest commit.
A Model is any value with Init, Update and View. This is
ExampleNewProgram from example_test.go; it reads keys from a
string and discards the output, so it runs without a terminal:
type counter struct{ n int }
func (c counter) Init() tui.Cmd { return nil }
func (c counter) Update(msg tui.Msg) (tui.Model, tui.Cmd) { if k, ok := msg.(tui.Key); ok && k.Type == tui.KeyRunes { switch k.Text { case "+": c.n++ case "q": return c, tui.Quit() } } return c, nil}
func (c counter) View() string { return fmt.Sprintf("count: %d", c.n) }
func ExampleNewProgram() { p := tui.NewProgram(counter{}, tui.WithInput(strings.NewReader("++q")), tui.WithOutput(io.Discard), ) final, err := p.Run() if err != nil { fmt.Println(err) return } fmt.Println(final.View()) // Output: count: 2}Against a real terminal, leave out WithInput and WithOutput: they default
to os.Stdin and os.Stdout. examples/counter does
exactly that:
$ go run ./examples/counterEvery directory under examples/ is a runnable program, for instance
./examples/dashboard, ./examples/form, ./examples/signup,
./examples/agentshell and ./examples/probe.
Packages
Section titled “Packages”API reference is the godoc of each package. The layering and stability levels
below come from the package comment in doc.go; a package may import
only the layers above it, and go test ./internal/archtest fails on an import
that points up.
Stability levels:
- Core: the root package
tui, the primitives, codecs andtuitest. - Stable-ish:
cellbuf, the render helpers and most components. - Experimental: packages whose own package comment says
Stability: experimental.go run ./internal/tools/doccheckfails if that list and the list indoc.godisagree.
Runtime
Section titled “Runtime”| Package | Purpose | Stability |
|---|---|---|
tui |
The Program and its event loop; owns terminal I/O through term and internal/termio. Re-exports 14 input types as aliases (Key, MouseEvent, PasteEvent, …), each as stable as the type it names |
Core |
cellbuf |
A retained grid of terminal cells, for widgets that draw straight into cells | Stable-ish |
Primitives
Section titled “Primitives”| Package | Purpose | Stability |
|---|---|---|
ansi |
Styles, colour, escape encoding, width, graphemes, wrapping | Core |
layout |
A Node tree with flex, grid and overlay layout | Core |
theme |
Colours, glyph sets and states as data | Core |
term |
Raw mode and terminal size | Core |
motion |
Springs, transitions and the reduced-motion preference | Core |
Codecs and ports
Section titled “Codecs and ports”| Package | Purpose | Stability |
|---|---|---|
input |
Decodes terminal bytes into keys, mouse, paste and focus events | Core |
keymap |
A registry of key bindings that help widgets read | Core |
hittest |
Named click regions: which region a mouse event landed on | Core |
Renderers and test harness
Section titled “Renderers and test harness”| Package | Purpose | Stability |
|---|---|---|
widgets |
Stateless render helpers: Badge, Box, CodeBlock, ChatMessage, DiffView, ProgressBar, … | Stable-ish |
widgets/chart |
Sparkline, BarChart, LineChart, HeatMap, Gauge | Stable-ish |
markdown |
A CommonMark subset rendered to styled, width-aware text | Stable-ish |
focus |
A focus Ring over components, moved with Tab and Shift+Tab | Stable-ish |
tuitest |
A headless harness that runs a model against a virtual screen | Core |
Components
Section titled “Components”One package per stateful widget, each a value-typed Model configured through
exported fields. Every widget has a LayoutNode() adapter, and
every widget implements Linearize for accessible mode; tests in
internal/archtest and internal/tools/doccheck fail when one is missing.
| Package | Purpose | Stability |
|---|---|---|
textinput |
Single-line text input | Stable-ish |
textarea |
Multi-line text input | Stable-ish |
passwordinput |
textinput that masks every character |
Stable-ish |
maskedinput |
textinput that masks with a configurable rune |
Stable-ish |
emailinput |
textinput that rejects whitespace |
Stable-ish |
numberinput |
textinput restricted to digits and a leading - |
Stable-ish |
taginput |
A list of short tags entered through a text input | Stable-ish |
autocomplete |
Text input with a filtered suggestion dropdown | Stable-ish |
form |
A column of labelled, validated single-line fields | Stable-ish |
confirm |
Yes/no prompt | Stable-ish |
picker |
Single-choice list | Stable-ish |
multiselect |
Multi-choice list | Stable-ish |
menu |
Nested-navigation list built on picker |
Stable-ish |
menubar |
Horizontal bar of titled dropdown menus | Stable-ish |
contextmenu |
Popup menu opened at an anchor point | Stable-ish |
datatable |
widgets.Table plus row navigation |
Stable-ish |
treeview |
Hierarchical expandable tree | Stable-ish |
filepicker |
Filesystem browse-and-select | Stable-ish |
datepicker |
Keyboard-navigable calendar on time.Time |
Stable-ish |
colorpicker |
Palette swatches plus hex input | Stable-ish |
virtuallist |
Scrollable window onto a large uniform-height list | Stable-ish |
viewport |
Scrollable window onto content taller than it | Stable-ish |
logview |
Append-only scrolling log | Stable-ish |
scrollbar |
Track and thumb showing the visible part of some content | Stable-ish |
splitpane |
Two panes with a draggable divider | Stable-ish |
tabs |
Horizontal tab bar | Stable-ish |
accordion |
List of collapsible sections | Stable-ish |
wizard |
Step navigation for multi-step flows | Stable-ish |
dialog |
Modal box composited over the screen | Stable-ish |
drawer |
Overlay anchored to an edge of the screen | Stable-ish |
popover |
Overlay anchored near a point | Stable-ish |
toast |
Transient auto-dismissing notification | Stable-ish |
helpscreen |
Full-screen key-binding help overlay | Stable-ish |
spinner |
Animated loading indicator | Stable-ish |
loadingbar |
Indeterminate progress animation | Stable-ish |
skeleton |
Loading placeholder block | Stable-ish |
clockview |
Wall clock, stopwatch or countdown timer | Stable-ish |
appshell |
Header, input, scrollable content and key-hints footer composed from existing widgets | Experimental |
streamtext |
Text revealed a few characters at a time | Experimental |
toolapproval |
Gate-before-execution prompt for an agent tool call | Experimental |
notificationcenter |
Panel showing every queued notification at once | Experimental |
commandpalette |
Text input with a fuzzy-filtered list of Commands | Experimental |
errorretry |
An error with retry and dismiss keys | Experimental |
faces |
50 animated Braille characters and a widget that plays them | Experimental |
imageview |
PNG through the kitty graphics protocol or Sixel, with a text placeholder otherwise | Experimental |
clipboard |
A “copy to clipboard” button that writes OSC 52 | Experimental |
Packages under internal/ are not public API.
Platforms and limitations
Section titled “Platforms and limitations”- Platforms. Linux, macOS (darwin), Windows, FreeBSD, OpenBSD, NetBSD and
DragonFly BSD. Any other GOOS, including solaris and illumos, is unsupported:
the build fails on purpose with an import named
tui_unsupported_platform_this_GOOS_is_not_supported_see_README(platform_unsupported.go). CI runs the tests on Linux, macOS, Windows and FreeBSD; OpenBSD, NetBSD and DragonFly BSD are only cross-compiled (TestBuildsOnSupportedPlatforms).WithSuspendOnCtrlZdoes nothing on Windows, where Ctrl+Z stays an ordinary key. - Colour. Without
WithColorProfile,NewProgramdetects the depth from the output and the environment (ansi.DetectColorProfileFor), in this order:NO_COLOR(non-empty) turns colour off;CLICOLOR_FORCE(non-empty, not0) colours output that is not a terminal; otherwise non-terminal output orCLICOLOR=0turns colour off;TERM=dumbturns colour off;COLORTERM=truecoloror24bit,WT_SESSION, and someTERM_PROGRAMvalues give 24-bit colour (Apple_Terminalgives 256); then theTERMname decides (an unrecognised name gets 16 colours; an emptyTERMgets none, except 16 on Windows). A truecolor terminal that sets none of these (some SSH sessions, tmux withTERM=screen) is detected at a lower depth; passWithColorProfile(ansi.TrueColor)to override. PassingWithColorProfilealso overridesNO_COLOR. - Unicode. Width, truncation and trimming treat an extended grapheme cluster
(a ZWJ emoji sequence, a flag, a letter with combining marks) as one unit,
following UAX #29. Set
TUI_NO_CLUSTERS=1for a terminal that draws the parts separately. The width, grapheme and bidi tables are from Unicode 17.0.0. Right-to-left text is reordered for display only withWithBidi(true); it is off by default. - Accessibility.
WithAccessible(true)switches to append-only, unstyled output and renders the root model’sLinearizeinstead ofViewwhen it has one.WithAccessibleAutoturns it on forACCESSIBLE=1orTERM=dumb, andTERM=dumbalone also turns it on unlessWithAltScreenorWithAccessiblewas given. VoiceOver, NVDA and Orca are unverified: no screen-reader run is recorded for this repository.
Terminal probe results
Section titled “Terminal probe results”examples/probe reports what a terminal supports
(colour depth, focus reporting, OSC 11 background detection) and leaves a
probe: line in the scrollback. No probe run is recorded for any terminal yet,
so every row is unverified. The middle column is what ansi/profile.go detects
from the environment alone, not a measured result.
| Terminal | Detected colour depth | Probe result |
|---|---|---|
Windows Terminal (WT_SESSION) |
24-bit | unverified |
iTerm2 (TERM_PROGRAM=iTerm.app) |
24-bit | unverified |
WezTerm (TERM_PROGRAM=WezTerm) |
24-bit | unverified |
VS Code (TERM_PROGRAM=vscode) |
24-bit | unverified |
Ghostty (TERM_PROGRAM=ghostty) |
24-bit | unverified |
Hyper (TERM_PROGRAM=Hyper) |
24-bit | unverified |
kitty (TERM=xterm-kitty) |
24-bit | unverified |
Alacritty (TERM=alacritty) |
24-bit | unverified |
Apple Terminal (TERM_PROGRAM=Apple_Terminal) |
256 colours | unverified |
Documentation
Section titled “Documentation”- docs/: topic guides, architecture and testing.
- CONTRIBUTING.md: conventions and the checks CI runs.
- CHANGELOG.md: user-visible changes.
License
Section titled “License”MIT; see LICENSE.