Skip to content
Updated May 2026Edit this page ↗

Architecture

TermUI is a monorepo with 13 packages. Each one does one thing and can be used on its own or combined with the others.

Dependencies flow downward; nothing in the core layer imports from the layers above it.

Package dependency graph

Four layers, each building on the one below:

APPLICATION LAYERCOMPONENT LAYERFEATURE LAYERCORE LAYER@termuijs/ui@termuijs/quickcreate-termui-app@termuijs/widgets@termuijs/jsx@termuijs/store@termuijs/tss@termuijs/router@termuijs/motion@termuijs/coreScreen · Input · Events · Layout · Style@termuijs/dataCPU · Memory · Disk · Network · Processes

Render pipeline

Every frame follows this path:

State Changedirty flag / storeLayout Engineflexbox algorithmStyle ResolveTSS variablesBuffer Diffprev → nextFlushstdout

Layout

The layout engine computes positions and sizes using a flexbox algorithm. It handles flexDirection, flexGrow, padding, margin, gap, and alignment.

All computed values are rounded to integers since terminal cells are discrete.

Style resolution

TSS variables and selectors are resolved. The theme engine substitutes var(--name) references and matches selectors against widget types and pseudo-classes like :focus.

Buffer diff

The screen holds two buffers. The renderer diffs them cell by cell and only writes changed cells to stdout.

A full-screen update that touches 3 cells writes exactly 3 escape sequences.

Event flow

Raw stdinbytesInputParserdecode sequencesEventEmitterbubble up treeKey Handlers+ app-level events

InputParser decodes raw bytes into KeyEvent objects with key name, modifiers, and raw bytes. Events bubble from the focused widget up through its parents to the app level.

Focus system

The focus system lives between @termuijs/jsx and @termuijs/ui. It provides a FocusContext that propagates the currently-focused widget ID down the tree via four hooks: useFocusManager (owns state), useFocus (reads/sets per widget), useFocusTrap (Tab capture for modals), and useKeyboardNavigation (arrow-key list navigation). See Focus Management for details.

State management

Three levels of state, each suited to different situations:

LevelAPIGood for
LocaluseStateState inside one component (cursor position, open/closed)
Shared configcreateContextData that rarely changes and many components need (theme, user)
Global reactivecreateStoreData that updates often and multiple components select from

Dev server

The dev server runs your entry file in a child process via child_process.fork(). When a source file changes, it kills the old process and spawns a fresh one.

Clean slate on every reload, no stale module cache.

File Changedebounced 200msFileWatcherfs.watchDevServerSIGTERM → fork()