textual-wasm

Ship one Textual application as both a terminal TUI and a client-side web page — same source, no server-side Python process, and no fork of Textual.

$ textual-wasm build myapp.main:App myapp -o dist/
built dist - 9 files, 240 KiB, 11 requirement(s)
$ textual-wasm dev dist/
serving dist on http://127.0.0.1:8000

The output is static files. Copy dist/ to any static host and it works: there is no server, no build toolchain at the far end, and nothing for the person deploying it to install. Pyodide and xterm.js are fetched from a pinned CDN; your application is written into the page as source.

This site is built with textual-wasm

Every demo in the examples gallery is a real build of a real Textual app, running in your browser on this GitHub Pages site. GitHub Pages serves static files and nothing else.

Why this works

Textual’s driver is an abstraction with an environment-variable hook — TEXTUAL_DRIVER=module:Symbol — that exists for out-of-tree drivers. Every syscall lives in textual/drivers/, the entry point is fully awaitable, and the input parser is pure synchronous Python. So a driver whose sink is an xterm.js terminal instead of a file descriptor is all it takes.

Nothing here patches Textual. The feasibility study is the audit that established that, and §11–§14 are what the implementation measured against it.

Start here

Quickstart

A working web build of a new app in about five minutes.

Quickstart
Porting an existing app

What to change, in the order you will hit it.

Porting an existing app
Embedding

Custom pages, and Textual inside Svelte, Vue or plain HTML.

Embedding and page customisation
The data channel

Live values between the app and the page, in both directions.

The data channel
Limitations

What does not work, and whose constraint each one is.

Limitations
Browser support

Measured across Chromium, Firefox and WebKit — and what that measurement does not cover.

Browser support

Commands

Command

textual-wasm doctor <module:App>

What will break, with a file:line. Reads imports, call sites and dependencies.

textual-wasm build <module:App> <package> -o dist/

A static site.

textual-wasm dev dist/

Serve it locally. Standard library only — no Node.

textual-wasm check --app <module:App>

Run the app on every runtime available and compare them.

check is the unusual one. It runs your application natively on CPython, under Pyodide in Node, in a real Chrome, and on a real pty through tmux — then compares the Python runtimes check by check and the real terminal against the browser cell by cell. A runtime this machine cannot reach is reported as skipped with the command that would enable it.