Skip to content
Shell & TerminalDeep Dive Published Updated 5 min readViews unavailable

What Makes a Terminal 'TUI-Capable': ncurses, terminfo, and Raw Mode

Full-screen terminal apps like htop and vim redraw the screen selectively and read keystrokes one at a time. This is the library layer that makes it possible.

Full-screen terminal applications like htop, vim, and lazygit behave nothing like a normal command printing output line by line — they take over the entire screen, redraw specific regions selectively, and respond to individual keystrokes immediately. A specific library layer is what makes this possible portably.

Why raw text output isn’t enough for a TUI

A normal command’s output is a stream of text the terminal displays line by line, scrolling as needed — a TUI (text user interface) application instead needs to move the cursor to arbitrary positions, redraw only specific changed regions, and read keystrokes as they happen rather than waiting for a full line ended by Enter.

The library that makes this portable: ncurses

ncurses (new curses, a free reimplementation of the older, AT&T-derived curses library) provides an API for exactly this: positioning the cursor anywhere on screen, drawing and clearing specific regions, handling color, and reading individual keystrokes — all without the application needing to know which specific terminal emulator it’s actually running inside.

How ncurses achieves that portability: terminfo

Different terminal emulators historically understood different, incompatible escape sequences for the same operation (like “move cursor to row 5, column 10”) — the terminfo database describes exactly which escape sequences a given terminal type (identified via the $TERM environment variable) actually supports, and ncurses consults it rather than hardcoding one specific terminal’s sequences.

Raw mode: the other essential piece

By default, a terminal operates in cooked mode (also called canonical mode) — input is buffered and only delivered to a program once a full line is submitted (with backspace and line-editing handled by the terminal driver itself). TUI applications instead put the terminal into raw mode, in which every individual keystroke is delivered to the program immediately, unbuffered, with no automatic line editing — necessary for an application to respond instantly to arrow keys or a q keypress rather than waiting for Enter.

Why a terminal sometimes looks broken after a TUI app crashes

If a TUI application crashes or is killed abruptly without properly restoring the terminal to cooked mode and its normal display state, the terminal can be left in raw mode with corrupted display settings — typed characters may not echo normally, or the screen may look wrong. This is exactly the scenario reset or stty sane exists to fix, restoring the terminal’s settings to sane defaults regardless of what state a crashed program left it in.

Why not every terminal environment supports every TUI application well

A genuinely minimal or non-standard terminal environment (a very constrained embedded serial console, certain non-interactive CI log capture contexts) may have a limited or inaccurate terminfo entry, or no real interactive raw-mode support at all — this is why some TUI applications degrade to a simpler text-only fallback mode, or refuse to run, in specific unusual environments.

Why understanding this layer explains TUI application behavior generally

Recognizing that TUI applications rely on ncurses (or an equivalent library), terminfo for portability, and raw mode for immediate keystroke handling demystifies a lot of otherwise-confusing behavior: why some old or obscure terminal types render TUI apps incorrectly, why a crashed TUI app can leave your terminal in a broken state, and why building a properly portable full-screen terminal application is considerably more involved than it might first appear.

Where curses itself came from, and why ncurses exists as a separate project

The original curses library — the name a pun on “cursor optimization,” its actual core function — was written by Ken Arnold at UC Berkeley in the late 1970s specifically to give full-screen BSD Unix programs like vi a portable way to address the screen. AT&T’s own System V curses implementation diverged from Berkeley’s over the 1980s, adding color and window support, and by the early 1990s the original BSD version had stalled; Zeyd Ben-Halim took over what had been a smaller free reimplementation called pcurses in late 1991 and released it under the new name ncurses (“new curses”) in November 1993, targeting compatibility with AT&T’s more feature-complete System V Release 4 curses API rather than the older BSD one. Eric S. Raymond drove development through the mid-1990s, adding the form and menu extension libraries, and Thomas E. Dickey has maintained the project since 1996 — meaning the library htop, vim, and countless other TUI applications link against today carries a continuous lineage back through three distinct maintainers to a 1970s Berkeley research project.

The specific optimization the name is actually about

ncurses’ defining technical trick isn’t drawing to the screen — it’s minimizing how much gets sent to do so. Internally, it maintains two in-memory representations of the display: one reflecting what an application has drawn via its API calls, and one reflecting what’s believed to actually be on the physical terminal right now. On each refresh, ncurses diffs the two, computes the shortest sequence of cursor moves and character writes needed to reconcile them, and sends only that — rather than redrawing the entire screen from scratch on every single update. This mattered enormously on the slow serial connections (as low as 300 to 9600 bits per second) curses was originally designed for, where retransmitting a full screen for a one-character change was genuinely costly; ncurses even lets a program declare the connection’s actual line speed so its optimizer can weigh cursor-repositioning costs against retransmission costs accurately. The optimization matters less on today’s fast local connections, but the underlying diffing algorithm is unchanged, and it’s the direct, literal reason the library is named after “cursor optimization” in the first place rather than simply “screen drawing.”

Related:

Sources:

Comments