opentui

Build terminal UIs with OpenTUI. Covers Core, frameworks, components, application APIs, testing, extensions, integrations, deployment, and public API lookup.

Install
npx skills add 'https://github.com/anomalyco/opentui/tree/main/packages/web/src/content'
Download bundle ↓
main · ac753b4Scanned 2026-09-15

Contributors

GitHub-linked commit authors for this SKILL.md at the saved revision. Co-authors and history before file renames are not included.

File history ↗

docs/getting-started/quickstart.mdx

docs/getting-started/quickstart.mdxBrowse 89 files
View on GitHub
← Back to SKILL.md

title: Quickstart description: Build and run an interactive OpenTUI app with Bun skill: entry: true intents: [installation, quickstart]

{/* docs-lint-disable mdx-esm-import */} import ScrollbackRecording from "../../../components/ScrollbackRecording.astro"

Quickstart

Build a small counter controlled by the arrow keys. The finished app uses layout, text, keyboard input, live updates, and explicit cleanup.

Prerequisite

This guide uses Bun to run TypeScript source. See Runtime and platform support for other runtime and platform requirements.

Create an empty project and add OpenTUI:

mkdir my-tui && cd my-tui
bun init -y
bun add @opentui/core

Create the app

Create index.ts:

import { BoxRenderable, TextRenderable, createCliRenderer } from "@opentui/core"

const renderer = await createCliRenderer({
  exitOnCtrlC: true,
  backgroundColor: "#1131E9",
})

let count = 0
const counter = new TextRenderable(renderer, {
  id: "counter",
  content: "Count  0",
  fg: "#FFFFFF",
})

const panel = new BoxRenderable(renderer, {
  width: 42,
  height: 9,
  backgroundColor: "#1131E9",
  alignItems: "center",
  justifyContent: "center",
})
const content = new BoxRenderable(renderer, {
  width: 38,
  height: 7,
  backgroundColor: "#2947F0",
  padding: 1,
  flexDirection: "column",
  gap: 1,
  alignItems: "center",
})

content.add(new TextRenderable(renderer, { content: "Hello, OpenTUI!", fg: "#DCE3FF" }))
content.add(counter)
content.add(new TextRenderable(renderer, { content: "left/right change | q quit", fg: "#AEBBFF" }))
panel.add(content)
renderer.root.add(panel)

renderer.keyInput.on("keypress", (key) => {
  if (key.name === "q") {
    renderer.destroy()
    return
  }

  if (key.name === "left") count--
  else if (key.name === "right") count++
  else return

  counter.content = `Count  ${count}`
})

createCliRenderer() takes control of the terminal and returns a CliRenderer. Its root property is the root of the component tree. BoxRenderable and TextRenderable are imperative tree nodes. The event handler updates the counter's content property directly.

The renderer emits parsed keyboard events through keyInput. The handler changes counter.content, which schedules a new frame.

Run it

bun index.ts

Press the Left and Right arrow keys. The count updates in place.

The application creates and owns the renderer. It must call renderer.destroy() on every shutdown path to release resources and restore the terminal. This example calls it when you press q. exitOnCtrlC: true handles the Ctrl+C path.

{/* docs-lint-disable mdx-component-node */}

<ScrollbackRecording label="The quickstart app changing a count with arrow keys, then returning to the shell" controls="hidden" stories={[ { src: "/recordings/quickstart.json", tone: "recorded", background: "recorded", padding: false, }, ]} />

Continue

Referenced from SKILL.md