Build websites that live in your terminal.

terminaltui is a TypeScript framework for interactive terminal apps. File-based routing, a 12-column grid, themes, and SSH hosting. Run with npx.

npx terminaltui try

A directory is the site map.

File paths become routes. Layouts wrap their siblings. Brackets become parameters. Nothing to configure.

my-site/ ├── config.ts ├── pages/ │ ├── layout.ts // wraps every page │ ├── home.ts → / │ ├── about.ts → /about │ ├── projects/ │ │ ├── index.ts → /projects │ │ └── [slug].ts → /projects/:slug │ └── contact.ts → /contact └── api/ ├── stats.ts → GET /api/stats └── subscribe.ts → POST /api/subscribe

Pages are functions

Each page exports a default function returning content blocks. export const metadata sets the menu label, icon, and order.

Layouts compose

A pages/layout.ts wraps siblings and descendants. Nested layouts compose from outside in.

Dynamic routes

Drop [slug].ts in any folder. The page receives params.slug at runtime.

API on the same filesystem

Files in api/ export GET(), POST(), etc. A local HTTP server starts alongside the TUI.

View router source on GitHub →

Eleven reference apps.

Each demo runs from npm with a single command. Hover any tile to preview the rendered terminal.

Everything a real app needs.

A focused, fully typed surface for UI, layout, state, data, routing, and testing.

30+ components

Card, Table, Timeline, Hero, Gallery, Tabs, Accordion, Quote, Badge, ProgressBar, Sparkline, Section, Divider.

Forms with validation

TextInput, TextArea, Select, Checkbox, Toggle, RadioGroup, NumberInput, SearchInput, Button. Validation and submission built in.

12-column grid

Bootstrap-style layout for terminals. Breakpoints at 60, 90, and 120 columns. Rows wrap, spans nest.

Spatial navigation

Arrow keys move to the nearest item by screen position. Distance and alignment scoring, no configuration.

File-based routing

Pages are files. Layouts wrap siblings. Dynamic routes use brackets. Async pages are first-class.

API routes

Files in api/ become endpoints. Export GET, POST, PUT, DELETE. The framework runs the server.

Ten built-in themes

Cyberpunk, Dracula, Nord, Monokai, Solarized, Gruvbox, Catppuccin, Tokyo Night, Rosé Pine, and Hacker. Custom themes too.

Reactive state

createState, computed values, persistent state, fetcher with auto-refresh, and live data over WebSocket and SSE.

Headless emulator

Spawn the app in a PTY. Read the screen, send keystrokes, assert content. Like Puppeteer for terminals.

Browse the framework source on GitHub →

Write a config. Drop in pages.

No JSX, no template language. Pages are TypeScript functions returning content blocks.

config.ts pages/about.ts pages/projects/[slug].ts api/stats.ts grid example
import { defineConfig } from "terminaltui";

export default defineConfig({
  name:   "My Site",
  theme:  "cyberpunk",
  banner: { text: "MY SITE", font: "ANSI Shadow" },

  // optional: serve over ssh
  serve: {
    port:           2222,
    maxConnections: 100,
  },
});
import { card, timeline, link } from "terminaltui";

export const metadata = {
  label: "About",
  icon:  "◆",
  order: 2,
};

export default function About() {
  return [
    card({
      title: "About Me",
      body:  "Full-stack developer based in Portland.",
    }),
    timeline([
      { date: "2026", title: "Started terminaltui" },
      { date: "2024", title: "Joined Acme Corp" },
    ]),
    link("GitHub", "https://github.com/me"),
  ];
}
import { card, section, badge } from "terminaltui";

type Params = { params: { slug: string } };

export default async function Project({ params }: Params) {
  const data = await fetch(
    `/api/projects/${params.slug}`
  ).then(r => r.json());

  return [
    section({ title: data.name, level: 1 }),
    badge(data.status, { tone: "success" }),
    card({ title: "Description", body: data.description }),
  ];
}
// api/stats.ts becomes GET /api/stats

export async function GET() {
  return {
    users:     45_231,
    uptime:    99.97,
    requests:  1_284_512,
  };
}

export async function POST(req: Request) {
  const body = await req.json();
  return { ok: true };
}
import { row, col, card, container } from "terminaltui";

export default function Dashboard() {
  return [
    container([
      row([
        col(statsCard, { span: 3, xs: 12 }),
        col(chartCard, { span: 9, xs: 12 }),
      ], { gap: 1 }),

      row([
        col([card({ title: "Revenue", body: "$1.2M" })], { span: 4 }),
        col([card({ title: "Users",   body: "45,231" })], { span: 4 }),
        col([card({ title: "Uptime",  body: "99.97%" })], { span: 4 }),
      ]),
    ], { maxWidth: 120, center: true })
  ];
}

Host any TUI over SSH.

One server, many sessions. Each connection is independent. Auto-detects the client TERM for color depth.

terminaltui serve --port 2222
ssh localhost -p 2222
Read the SSH server implementation →

Get started.

Five commands.

01
npx terminaltui try
Take a 5-page guided tour. No install.
02
npx terminaltui init my-site
Scaffold a new project from a template.
03
npx terminaltui dev
Compile and run live in your terminal.
04
terminaltui serve
Optional. Host over SSH.
05
npm publish
Anyone can run npx your-site.
terminaltui init [template]Scaffold a new project from a template.
terminaltui dev [path]Compile and run.
terminaltui serve [path]Host the TUI over SSH.
terminaltui buildBundle for npm publish.
terminaltui demo [name]Run a built-in demo.
terminaltui testRun headless emulator tests.