Terminal user interfaces (TUIs)
Interactive programs that draw their whole interface as text in a terminal window, with panels, lists and keyboard control, and the libraries used to build them.
3 technologies on this page ↓How it works
A terminal is a grid of character cells. A program draws on it by printing text mixed with escape sequences: byte strings that start with the ESC character and move the cursor, set colors or clear part of the screen, a convention standardized as ECMA-48 and ANSI X3.64 and spread by DEC’s VT100. A full-screen TUI first puts the terminal into raw mode (with termios on Unix), so each keypress arrives at once, unechoed and without line editing. It then usually switches to the alternate screen buffer (CSI ? 1049 h in xterm): a second screen exactly the size of the window, with no scrollback, that the app can repaint freely without disturbing the shell that started it. On exit it switches back and the earlier output reappears. Keypresses, and mouse clicks, which xterm-style terminals also report as escape sequences, feed an event loop that updates the app’s state and redraws. Repainting every cell on each change sends more characters and flickers, so libraries keep two copies of the screen, what is shown and what should be, and send only the difference: curses calls them the physical and virtual screens, and Ratatui renders each frame into a buffer and diffs it against the previous one. Libraries differ most in how you describe the interface. Ratatui is immediate mode: your code redraws the whole UI from application state every frame. Bubble Tea follows the Elm Architecture: a model, an Update function that turns each message into a new model, and a View that renders it. Textual keeps a tree of widgets, which it calls the DOM, styled with its own CSS, and the same app can run in a browser. Differences between terminals are looked up in terminfo, a database describing thousands of them, and Windows consoles interpret the same VT sequences once a program enables ENABLE_VIRTUAL_TERMINAL_PROCESSING.
A split-flap departure board. It’s a fixed grid of letter tiles; to show a new train it flips only the tiles that change, not the whole board, and it can be read from anywhere the board hangs.
Reach for it when
- Tools people use where they already work: in a shell, inside a container, or on a server over SSH, such as htop, lazygit or k9s
- Keyboard-driven dashboards and browsers over data that changes: processes, logs, queues, Git history, cluster resources
- Developer tools that need more than a prompt but not a desktop app
Think twice when
- Users who don’t live in a terminal, or interfaces that need images, fine-grained charts or drag and drop
- Output other programs will read: a TUI’s screen is escape sequences, so also offer a plain-text or JSON mode for scripts and pipes
A real example
A developer keeps lazygit open in a terminal split beside the editor. Staged files, branches and the commit log are panels; a keystroke stages a file, and the library rewrites only the few cells that changed rather than all 4,000 or so on a 160 × 26 screen. The same works in an SSH session on a build server. Quitting puts the terminal back in normal mode and switches off the alternate screen, so the shell’s scrollback is exactly as it was.
Technologies
Specific products, and what each does differently.
Bubble TeaOpen sourceA Go framework from Charm for terminal apps built on the Elm Architecture: one model holds the state, Update turns messages into a new model, and View describes the screen. Bubble Tea | Open source | Charmbracelet, Inc. | MIT | 2026-10-13 |
| Open source | The Ratatui Developers (forked from tui-rs by Florian Dehau) | MIT | 2026-10-13 | |
| Open source | Will McGugan and contributors (built at Textualize, Inc. until mid-2025) | MIT | 2026-10-13 |
Libraries and how they model the screen#
Every TUI library ends up writing escape sequences to the same character grid; they differ in how your code describes what should be on it.
| Library | Language | How you describe the UI |
|---|---|---|
| curses / ncurses | C, with bindings for many languages | Write into windows, then call refresh(); curses diffs a virtual screen against the physical one |
| Ratatui | Rust | Immediate mode: redraw the whole UI from state every frame into a buffer, which is diffed against the previous frame |
| Bubble Tea | Go | The Elm Architecture: a model, Update for each message, View to render; inline or full-window |
| Textual | Python | A tree of widgets (a DOM) styled with CSS, asynchronous underneath; also runs in a web browser |
| Ink | JavaScript (Node.js) | React components rendered to the terminal |
In code · bash
# What a TUI library does under the hood, by hand in a shell
stty raw -echo # keys arrive one at a time, not echoed
printf '\033[?1049h' # switch to the alternate screen
printf '\033[2J\033[H' # clear it, cursor to row 1, column 1
printf '\033[1;32mDeploys\033[0m' # bold green text, then reset
printf '\033[3;1H> api ok' # draw at row 3, column 1
printf '\033[4;1H web failed' # and row 4
key=$(dd bs=1 count=1 2>/dev/null) # wait for one keypress
printf '\033[?1049l' # back to the normal screen, scrollback intact
stty sane # restore echo and line editingWorth exploring
Often confused with
Where it came from1976–2023
Early video terminals each had their own control codes; a VT52 moved the cursor with one sequence and a Hazeltine 1500 with another, so programs relied on libraries such as termcap to speak to any of them. The ECMA-48 standard of 1976 and ANSI X3.64 of 1979 defined one set of escape sequences, and DEC’s VT100, introduced in August 1978, was one of the first terminals to support them. It sold so well that its codes became the de facto standard for hardware terminals and, later, for terminal emulators.
Full-screen programs needed a layer that hid the remaining differences. At the University of California, Berkeley, students wrote the screen handling for the vi editor on top of the termcap database, then turned those routines into the curses library, first published in 4.0BSD in late 1980. One of them later built terminfo at Bell Labs, released with System V Release 2 in early 1984, and a rewritten curses followed in 1986. Pavel Curtis’s free pcurses became the base of ncurses, whose first widely used version, 1.8.1, appeared in November 1993; ncurses is still maintained today.
A new generation of libraries borrowed ideas from games and the web. Florian Dehau’s tui-rs brought an immediate-mode design to Rust; it is no longer maintained, and in 2023 it was forked as Ratatui to continue its development. Charm’s Bubble Tea applied the Elm Architecture to Go, and Will McGugan’s Textual gave Python a widget tree styled with CSS that can also run in a browser. Justin Garrison’s awesome-tuis list now catalogs more than 700 terminal apps and libraries, from system monitors to Kubernetes dashboards.
Why it’s built this way
- Escape sequences in the same byte stream as the text
- No second channel is needed: anything that can print can move the cursor or change color, so the same app works over a 1978 serial line or a modern SSH connection.
- Describe terminals in a database instead of hard-coding them
- termcap and then terminfo let one program run on thousands of terminal types: curses looks up each terminal’s capabilities and emits the right codes.
- Send only what changed
- curses keeps a virtual and a physical screen and transmits the difference; batching several windows into one update sends fewer characters and “avoids a visually annoying flicker”. Ratatui uses the same double-buffer idea.
- Borrow architectures from games and the web
- Immediate mode ties the screen directly to state, with no widget objects to keep in sync; the Elm Architecture funnels every event through one
Update; Textual’s DOM and CSS let web developers reuse what they know.
Timeline
- 1976
ECMA-48 standardizes terminal control sequences
- 1978
DEC introduces the VT100 (August)
- 1980
The curses library is published in 4.0BSD
- 1984
terminfo ships with System V Release 2
- 1993
ncurses 1.8.1, the first widely used version (November)
- 2023
Ratatui forks tui-rs to keep it developed
- Wikipedia: ANSI escape code (vendor-specific codes, ECMA-48, ANSI X3.64)
- Wikipedia: VT100 (introduced August 1978, de facto standard)
- Eric S. Raymond and Zeyd M. Ben-Halim: Writing Programs with NCURSES (history of curses and terminfo)
- Thomas E. Dickey: NCURSES Frequently Asked Questions (pcurses, ncurses 1.8.1 in November 1993)
- Ratatui README: forked from tui-rs in 2023
- GitHub: fdehau/tui-rs (archived)
- Bubble Tea README: based on the Elm Architecture
- Textual README: Python UIs for the terminal or a web browser
- Ratatui docs: Rendering (immediate mode)
- Ratatui docs: Rendering under the hood (double buffer and diff)
- rothgar/awesome-tuis on GitHub (Justin Garrison)
Sources12
- xterm Control Sequences (alternate screen buffer, mode 1049, mouse tracking)
- termios(3), Linux manual page (canonical, noncanonical and raw mode)
- Microsoft Learn: Console virtual terminal sequences (alternate screen buffer, ENABLE_VIRTUAL_TERMINAL_PROCESSING)
- Writing Programs with NCURSES (terminfo, virtual and physical screens, refresh)
- Ratatui docs: Rendering (immediate mode)
- Ratatui docs: Rendering under the hood (buffers and diffing)
- Bubble Tea README: model, Update and View; inline or full-window
- Textual README: terminal or web browser, asynchronous framework
- Textual docs: Textual CSS (the DOM as a tree of widgets)
- Wish README: render TUIs directly over an SSH connection
- awesome-tuis README: TUI applications and libraries by category (htop, lazygit, k9s, Ink)
- Wikipedia: ANSI escape code