A dependency-free TypeScript terminal UI runtime. Screens are plain data; JSX is one way to write them.
import { createApp, registerBuiltins } from '@textui/core';
import { createNodeTerminal } from '@textui/terminal';
const app = createApp({
terminal: createNodeTerminal(),
root: <Dashboard />,
onBoot: registerBuiltins,
});
await app.start();Status: pre-1.0. The surface is still moving.
JSX compiles to data. <Row gap={1}/> and { component: 'Row', gap: 1 } are the same value, and the runtime mounts either. A screen can be written in TypeScript, loaded from JSON, generated, edited or sent over a wire without the runtime changing - and components are resolved by name at mount time, so what renders is a registration rather than an import.
Everything else follows from that: one reactive store addressed by paths, typed registries for components, commands, themes, shells and resources, and a renderer that diffs cells rather than redrawing frames.
| Package | What it is |
|---|---|
@textui/core |
The runtime: store, registries, renderer, hooks, component catalog |
@textui/terminal |
Terminal adapters, capability detection, ANSI writing, input decoding |
@textui/testing |
Headless harness: semantic queries, input, resizing, time |
@textui/cli |
textui init / add / create / doctor, and primitives for your own CLI |
components/ |
The source-copy registry - components you own, not import |
playground/ |
The showcase, fourteen focused playgrounds, and a filesystem explorer |
Published at https://softov.github.io/textui/, and readable in
docs/ as plain markdown.
Start here:
| Document | What it answers |
|---|---|
| Getting started | From nothing to a running application |
| The vocabulary | The words everything else assumes |
| Architecture | The model: store, graph, registries, surfaces, shells |
| Decisions and tradeoffs | What was chosen, and what it cost |
Then by subsystem:
| Section | What it covers |
|---|---|
| Store | Paths, scopes, computed, collections, providers, events |
| Components | The catalog, how to write one, and the templates |
| Themes | Tokens, glyphs, borders, capability downgrade, syntax |
| Platform | Commands, keybindings, focus, layers, screens, extension points |
| Terminal | Adapters, capabilities, managed and embedded sessions |
| Documents | Resource kinds, providers, viewers, editors, buffers |
| CLI | The developer CLI and the registry model |
| Testing | The harness, and what to assert |
pnpm install
pnpm build # every package
pnpm typecheck # every workspace
pnpm test # every suite
pnpm dev --list # the playgrounds
pnpm dev gallery # open oneThe docs site is Jekyll, and needs no Ruby on your machine - it builds in a container:
scripts/docs-serve.sh # live, with reload, at localhost:4000/textui/
scripts/docs-serve.sh --build # build once, into docs/_site
scripts/docs-preview.py # serve what was built, at localhost:8000/textui/
scripts/docs-preview.py --host 0.0.0.0 # ...and reachable from the network
node scripts/check-docs.mjs # the nav tree, links and titlesdocs-preview.py exists because the site is built with baseurl: /textui, so
every link in it is absolute at /textui/.... A plain python -m http.server
over docs/_site 404s on all of it; this one mounts the site under the prefix
the pages actually ask for.
Node ≥ 22, pnpm 10.
The three layouts this project started from - a dense bordered console, an airy borderless report, and a workbench frame - are one architecture with three registrations. playground/test/playgrounds.test.tsx mounts the same component under all of them, and under six themes, at three terminal widths, with and without Unicode and colour. If a shell ever needs a component the others cannot use, the boundary is in the wrong place.
MIT