···11+---
22+title: API Overview
33+description: Overview of the @bomb.sh/tty public API
44+---
55+66+`@bomb.sh/tty` exports five modules from a single entry point. Everything is available from `@bomb.sh/tty`:
77+88+```ts twoslash
99+import {
1010+ open, text, close, snapshot, rgba, fit, grow, percent, fixed,
1111+ createTerm,
1212+ createInput,
1313+ settings, alternateBuffer, cursor, mouseTracking,
1414+} from "@bomb.sh/tty";
1515+```
1616+1717+## Modules
1818+1919+| Module | Description |
2020+|---|---|
2121+| [Ops](/docs/tty/api/ops) | UI directive builders: `open`, `text`, `close`, sizing, colors, transitions |
2222+| [Term](/docs/tty/api/term) | Renderer: `createTerm`, `render`, pointer events, layout info |
2323+| [Input](/docs/tty/api/input) | Input parser: `createInput`, `scan`, keyboard/mouse/resize events |
2424+| [Settings](/docs/tty/api/settings) | Terminal mode helpers: alternate buffer, cursor, mouse tracking |
2525+| [Termcodes](/docs/tty/api/termcodes) | Low-level ANSI escape sequence bytes |
2626+2727+## Typical app structure
2828+2929+1. Apply [settings](/docs/tty/api/settings) to stdout (alternate buffer, cursor, mouse)
3030+2. Create a [term](/docs/tty/api/term) and [input](/docs/tty/api/input) instance
3131+3. Set stdin to raw mode
3232+4. On each frame: build [ops](/docs/tty/api/ops) → `term.render()` → write `output`
3333+5. On stdin data: `input.scan()` → handle events
3434+6. If `renderResult.animating`, schedule another frame
3535+7. On exit: revert settings and restore terminal state
3636+3737+## Deno-only exports
3838+3939+The Deno source tree includes `@bomb.sh/tty/validate` with runtime op validation (`validate`, `assert`, `validated`). This subpath is not in the published npm `exports` map for v0.8.0.
4040+4141+## Next steps
4242+4343+- [Getting Started](/docs/tty/basics/getting-started)
4444+- [Examples](/docs/tty/guides/examples)
+127
src/content/docs/tty/api/input.mdx
···11+---
22+title: Input
33+description: Terminal input parser API for @bomb.sh/tty
44+---
55+66+import { Aside } from '@astrojs/starlight/components';
77+88+The input module decodes raw terminal bytes into structured events. All parsing logic lives in WASM. The TypeScript layer is a thin wrapper.
99+1010+## createInput(options?)
1111+1212+Creates an input parser instance.
1313+1414+```ts twoslash
1515+import { createInput } from "@bomb.sh/tty";
1616+1717+async function main() {
1818+ let input = await createInput({ escLatency: 25 });
1919+}
2020+```
2121+2222+### InputOptions
2323+2424+| Option | Type | Default | Description |
2525+|---|---|---|---|
2626+| `escLatency` | `number` | `25` | Milliseconds to wait before resolving a lone ESC as the Escape key |
2727+| `terminfo` | `Uint8Array` | xterm defaults | Compiled terminfo binary for terminal-specific sequences |
2828+2929+<Aside type="note">
3030+Lower `escLatency` feels snappier but risks misinterpreting multi-byte sequences on slow connections. Vim's `ttimeoutlen` defaults to 100ms; ncurses `ESCDELAY` defaults to 1000ms.
3131+</Aside>
3232+3333+## input.scan(bytes?)
3434+3535+Feed raw bytes from stdin and return parsed events. Call with no arguments to flush a pending ESC after the latency period.
3636+3737+```ts twoslash
3838+import { createInput } from "@bomb.sh/tty";
3939+4040+async function main() {
4141+ let input = await createInput({ escLatency: 25 });
4242+4343+ process.stdin.setRawMode(true);
4444+ let timer: ReturnType<typeof setTimeout> | undefined;
4545+4646+ process.stdin.on("data", (buf) => {
4747+ clearTimeout(timer);
4848+4949+ let { events, pending } = input.scan(new Uint8Array(buf));
5050+5151+ for (let event of events) {
5252+ console.log(event);
5353+ }
5454+5555+ if (pending) {
5656+ timer = setTimeout(() => {
5757+ let flush = input.scan();
5858+ for (let event of flush.events) {
5959+ console.log(event);
6060+ }
6161+ }, pending.delay);
6262+ }
6363+ });
6464+}
6565+```
6666+6767+### ScanResult
6868+6969+| Property | Type | Description |
7070+|---|---|---|
7171+| `events` | `InputEvent[]` | Events produced from this scan |
7272+| `pending` | `{ delay: number }` | Present when a lone ESC is buffered. Re-call `scan()` after `delay` ms |
7373+7474+## Event types
7575+7676+### Keyboard events
7777+7878+| Type | Description |
7979+|---|---|
8080+| `keydown` | Key was pressed |
8181+| `keyrepeat` | Key auto-repeat (Kitty enhancement level 2+) |
8282+| `keyup` | Key was released (Kitty enhancement level 2+) |
8383+8484+All keyboard events include:
8585+8686+| Property | Type | Description |
8787+|---|---|---|
8888+| `key` | `string` | Key value (e.g. `"a"`, `"Enter"`, `"ArrowUp"`) |
8989+| `code` | `KeyCode` | Physical key identity (US PC-101 layout) |
9090+| `text` | `string?` | Typed character, if applicable |
9191+| `shifted` | `string?` | Shifted character variant |
9292+| `alt`, `ctrl`, `shift` | `true?` | Modifier keys held |
9393+9494+### Mouse events
9595+9696+| Type | Description |
9797+|---|---|
9898+| `mousedown` | Button pressed (`left`, `right`, `middle`) |
9999+| `mouseup` | Button released |
100100+| `mousemove` | Movement while button held |
101101+| `wheel` | Scroll wheel (`direction: "up"` \| `"down"`) |
102102+103103+Mouse events include `x` and `y` coordinates (0-based) and optional modifiers.
104104+105105+### Other events
106106+107107+| Type | Description |
108108+|---|---|
109109+| `resize` | Terminal resized. Returns `{ width, height }` |
110110+| `cursor` | DSR cursor position report. Returns `{ row, column }` (1-based) |
111111+112112+Pointer events (`pointerenter`, `pointerleave`, `pointerclick`) are produced by the [renderer](/docs/tty/api/term), not the input parser.
113113+114114+## Supported protocols
115115+116116+The parser recognizes:
117117+118118+- VT/ANSI escape sequences
119119+- UTF-8 codepoints
120120+- Mouse protocols (VT200, SGR, urxvt)
121121+- Progressive keyboard protocol (via [settings](/docs/tty/api/settings))
122122+- Partial sequence reassembly across read boundaries
123123+124124+## Next steps
125125+126126+- [Settings API](/docs/tty/api/settings) (enable mouse tracking and keyboard protocols)
127127+- [Term API](/docs/tty/api/term) (pointer hit testing on the render side)
+168
src/content/docs/tty/api/ops.mdx
···11+---
22+title: Ops
33+description: UI directive builders for @bomb.sh/tty
44+---
55+66+import { Aside } from '@astrojs/starlight/components';
77+88+The ops module provides builders for describing terminal UI as a flat array of directives. Each frame is a complete snapshot. Build ops, pass them to [`term.render()`](/docs/tty/api/term), and write the output bytes.
99+1010+## Building a frame
1111+1212+```ts twoslash
1313+import { close, open, rgba, text, grow } from "@bomb.sh/tty";
1414+1515+let ops = [
1616+ open("root", {
1717+ layout: { width: grow(), height: grow(), direction: "ttb" },
1818+ }),
1919+ open("panel", {
2020+ layout: { padding: { left: 1, right: 1, top: 1, bottom: 1 } },
2121+ border: { color: rgba(0, 255, 0), left: 1, right: 1, top: 1, bottom: 1 },
2222+ cornerRadius: { tl: 1, tr: 1, bl: 1, br: 1 },
2323+ }),
2424+ text("Hello, World!"),
2525+ close(),
2626+ close(),
2727+];
2828+```
2929+3030+## open(id, props?)
3131+3232+Opens a layout element. Every element needs a unique string `id` for pointer hit-testing and layout queries.
3333+3434+### Layout
3535+3636+| Property | Type | Description |
3737+|---|---|---|
3838+| `width` | `SizingAxis` | Horizontal sizing (`fit`, `grow`, `percent`, `fixed`) |
3939+| `height` | `SizingAxis` | Vertical sizing |
4040+| `padding` | `{ left?, right?, top?, bottom? }` | Inner padding in cells |
4141+| `gap` | `number` | Gap between children |
4242+| `direction` | `"ltr"` \| `"ttb"` | Child layout direction |
4343+| `alignX` | `"left"` \| `"center"` \| `"right"` | Horizontal alignment |
4444+| `alignY` | `"top"` \| `"center"` \| `"bottom"` | Vertical alignment |
4545+4646+### Style
4747+4848+| Property | Type | Description |
4949+|---|---|---|
5050+| `bg` | `number` | Background color (packed RGBA via `rgba()`) |
5151+| `cornerRadius` | `{ tl?, tr?, bl?, br? }` | Rounded corners |
5252+| `border` | `{ color, bg?, left?, right?, top?, bottom? }` | Border sides (number or `{ width, color?, bg? }`) |
5353+| `clip` | `{ horizontal?, vertical? }` | Clip overflowing content |
5454+5555+### Floating
5656+5757+| Property | Type | Description |
5858+|---|---|---|
5959+| `floating.x`, `floating.y` | `number` | Position offset |
6060+| `floating.expand` | `{ width?, height? }` | Expand floating bounds |
6161+| `floating.attachTo` | `"none"` \| `"parent"` \| `"element"` \| `"root"` | Attachment target |
6262+| `floating.attachPoints` | `{ element?, parent? }` | Anchor points |
6363+| `floating.pointerCaptureMode` | `"capture"` \| `"passthrough"` | Pointer event capture |
6464+| `floating.clipTo` | `"none"` \| `"attached-parent"` | Clip to attached parent |
6565+| `floating.zIndex` | `number` | Stacking order |
6666+6767+### Transitions
6868+6969+```ts twoslash
7070+import { open, rgba } from "@bomb.sh/tty";
7171+7272+open("sidebar", {
7373+ layout: { width: { type: "fixed", value: 20 } },
7474+ bg: rgba(30, 30, 40),
7575+ transition: {
7676+ duration: 0.3,
7777+ easing: "easeInOut",
7878+ properties: ["width", "bg"],
7979+ },
8080+});
8181+```
8282+8383+| Property | Type | Description |
8484+|---|---|---|
8585+| `duration` | `number` | Duration in seconds |
8686+| `easing` | `"linear"` \| `"easeIn"` \| `"easeOut"` \| `"easeInOut"` | Easing function |
8787+| `properties` | `TransitionProperty[]` | Properties to animate |
8888+| `interactive` | `boolean` | Allow interaction during transition |
8989+9090+Transition properties: `"x"`, `"y"`, `"position"`, `"width"`, `"height"`, `"size"`, `"bg"`, `"overlay"`, `"borderColor"`, `"borderWidth"`, `"all"`.
9191+9292+## text(content, props?)
9393+9494+Renders a text node inside the current open element.
9595+9696+| Property | Type | Description |
9797+|---|---|---|
9898+| `color` | `number` | Foreground color |
9999+| `bg` | `number` | Background color |
100100+| `fontSize` | `number` | Font size |
101101+| `fontId` | `number` | Font identifier |
102102+| `wrap` | `number` | Wrap width |
103103+| `attrs` | `number` | Text attributes (bold, italic, etc.) |
104104+105105+## close()
106106+107107+Closes the current open element. Ops must be properly nested. Every `open` needs a matching `close`.
108108+109109+## snapshot(ops)
110110+111111+Pre-packs an op array into a reusable snapshot op. Useful when part of the UI is static across frames.
112112+113113+```ts twoslash
114114+import { close, open, snapshot, text, grow } from "@bomb.sh/tty";
115115+116116+let chrome = snapshot([
117117+ open("header", { layout: { width: grow(), height: { type: "fixed", value: 1 } } }),
118118+ text("My App"),
119119+ close(),
120120+]);
121121+122122+// Each frame: [chrome, ...dynamicOps]
123123+```
124124+125125+## rgba(r, g, b, a?)
126126+127127+Packs an RGBA color into a 32-bit integer. Alpha defaults to 255.
128128+129129+```ts twoslash
130130+import { rgba } from "@bomb.sh/tty";
131131+132132+rgba(255, 0, 0); // red
133133+rgba(0, 255, 0, 128); // semi-transparent green
134134+```
135135+136136+## Sizing helpers
137137+138138+```ts twoslash
139139+import { fit, grow, percent, fixed } from "@bomb.sh/tty";
140140+141141+fit(); // shrink to content
142142+fit(10, 40); // fit with min/max
143143+grow(); // expand to fill available space
144144+grow(5, 50); // grow with min/max
145145+percent(0.5); // 50% of parent
146146+fixed(20); // exactly 20 cells
147147+```
148148+149149+## Types
150150+151151+| Type | Description |
152152+|---|---|
153153+| `Op` | Union of all directive types |
154154+| `OpenElement` | Element opened by `open()` |
155155+| `Text` | Text node |
156156+| `CloseElement` | Element closed by `close()` |
157157+| `SizingAxis` | `{ type: "fit" \| "grow" \| "percent" \| "fixed", ... }` |
158158+| `Transition` | Transition configuration |
159159+| `BorderSide` | `number` or `{ width, color?, bg? }` |
160160+161161+<Aside type="note">
162162+`pack()` is also exported for low-level wire-format packing. Most applications use `open`/`text`/`close` and let `term.render()` handle packing internally.
163163+</Aside>
164164+165165+## Next steps
166166+167167+- [Term API](/docs/tty/api/term) (rendering ops to the terminal)
168168+- [Examples](/docs/tty/guides/examples) (transitions and layout demos)
+142
src/content/docs/tty/api/settings.mdx
···11+---
22+title: Settings
33+description: Terminal mode helpers for @bomb.sh/tty
44+---
55+66+The settings module provides composable terminal mode changes. Each setting produces `apply` and `revert` byte sequences you write to stdout.
77+88+## `Setting`
99+1010+Every setting helper returns:
1111+1212+```ts
1313+interface Setting {
1414+ apply: Uint8Array;
1515+ revert: Uint8Array;
1616+}
1717+```
1818+1919+Write `apply` on startup and `revert` on exit to restore the terminal.
2020+2121+## `settings(...sequence)`
2222+2323+Compose multiple settings into one. Revert runs in reverse order.
2424+2525+```ts twoslash
2626+import {
2727+ alternateBuffer,
2828+ cursor,
2929+ mouseTracking,
3030+ settings,
3131+} from "@bomb.sh/tty";
3232+3333+let tty = settings(
3434+ alternateBuffer(),
3535+ cursor(false),
3636+ mouseTracking(),
3737+);
3838+3939+process.stdout.write(tty.apply);
4040+4141+// on exit:
4242+process.stdout.write(tty.revert);
4343+```
4444+4545+## `alternateBuffer(options?)`
4646+4747+Switch to the alternate screen buffer.
4848+4949+| Option | Type | Default | Description |
5050+|---|---|---|---|
5151+| `clear` | `boolean` | `true` | Clear the alternate buffer on entry |
5252+5353+```ts twoslash
5454+import { alternateBuffer } from "@bomb.sh/tty";
5555+5656+alternateBuffer(); // enter, clear
5757+alternateBuffer({ clear: false }); // enter, preserve contents
5858+```
5959+6060+Revert switches back to the main screen with scrollback intact.
6161+6262+## `cursor(visible)`
6363+6464+Show or hide the terminal cursor (DEC private mode 25).
6565+6666+```ts twoslash
6767+import { cursor } from "@bomb.sh/tty";
6868+6969+cursor(false); // hide
7070+cursor(true); // show
7171+```
7272+7373+## `saveCursorPosition()`
7474+7575+Save and restore cursor position using DECSC (`ESC 7`) / DECRC (`ESC 8`).
7676+7777+```ts twoslash
7878+import { saveCursorPosition } from "@bomb.sh/tty";
7979+8080+let pos = saveCursorPosition();
8181+process.stdout.write(pos.apply);
8282+// ... render ...
8383+process.stdout.write(pos.revert);
8484+```
8585+8686+## `progressiveInput(level)`
8787+8888+Enable the progressive keyboard enhancement protocol.
8989+9090+```ts twoslash
9191+import { progressiveInput } from "@bomb.sh/tty";
9292+9393+progressiveInput(2); // Kitty enhancement level 2+
9494+```
9595+9696+Revert sends `CSI <u` to disable the protocol.
9797+9898+## `mouseTracking()`
9999+100100+Enable SGR mouse reporting (modes 1003 and 1006). Required for pointer hit testing with [`term.render()`](/docs/tty/api/term).
101101+102102+```ts twoslash
103103+import { mouseTracking } from "@bomb.sh/tty";
104104+105105+let mouse = mouseTracking();
106106+process.stdout.write(mouse.apply);
107107+```
108108+109109+Pair with the [input parser](/docs/tty/api/input) to receive mouse events from stdin.
110110+111111+## Typical lifecycle
112112+113113+```ts twoslash
114114+import {
115115+ alternateBuffer,
116116+ cursor,
117117+ mouseTracking,
118118+ settings,
119119+ createTerm,
120120+ createInput,
121121+} from "@bomb.sh/tty";
122122+123123+async function main() {
124124+ let tty = settings(alternateBuffer(), cursor(false), mouseTracking());
125125+ process.stdout.write(tty.apply);
126126+127127+ let term = await createTerm({ width: 80, height: 24 });
128128+ let input = await createInput();
129129+130130+ process.stdin.setRawMode(true);
131131+132132+ // ... app loop ...
133133+134134+ process.stdin.setRawMode(false);
135135+ process.stdout.write(tty.revert);
136136+}
137137+```
138138+139139+## Next steps
140140+141141+- [Termcodes API](/docs/tty/api/termcodes) (low-level escape sequences)
142142+- [Getting Started](/docs/tty/basics/getting-started) (full quick start)
+172
src/content/docs/tty/api/term.mdx
···11+---
22+title: Term
33+description: Terminal renderer API for @bomb.sh/tty
44+---
55+66+import { Aside } from '@astrojs/starlight/components';
77+88+The term module creates a WASM-backed renderer that converts UI ops into ANSI escape sequences.
99+1010+## createTerm(options)
1111+1212+Creates a renderer instance. Both dimensions are required.
1313+1414+```ts twoslash
1515+import { createTerm } from "@bomb.sh/tty";
1616+1717+async function main() {
1818+ let term = await createTerm({ width: 80, height: 24 });
1919+}
2020+```
2121+2222+| Option | Type | Description |
2323+|---|---|---|
2424+| `width` | `number` | Terminal width in columns |
2525+| `height` | `number` | Terminal height in rows |
2626+2727+Returns a `Term` with a single `render()` method.
2828+2929+## term.render(ops, options?)
3030+3131+Renders a frame and returns the result.
3232+3333+```ts twoslash
3434+import { close, createTerm, open, text, grow } from "@bomb.sh/tty";
3535+3636+async function main() {
3737+ let term = await createTerm({ width: 80, height: 24 });
3838+3939+ let { output, events, info, errors, animating } = term.render([
4040+ open("root", { layout: { width: grow(), height: grow() } }),
4141+ text("Hello"),
4242+ close(),
4343+ ]);
4444+4545+ process.stdout.write(output);
4646+}
4747+```
4848+4949+### RenderOptions
5050+5151+| Option | Type | Description |
5252+|---|---|---|
5353+| `pointer` | `{ x, y, down }` | Pointer position and button state for hit testing |
5454+| `deltaTime` | `number` | Seconds since last frame (for transitions). Auto-computed if omitted |
5555+| `mode` | `"line"` | Render into a line region instead of full screen |
5656+| `row` | `number` | Starting row for line mode (1-based, DSR format) |
5757+5858+### Pointer detection
5959+6060+Pass pointer state to get hit-testing events alongside output:
6161+6262+```ts twoslash
6363+import { close, createTerm, fixed, grow, open, text } from "@bomb.sh/tty";
6464+6565+async function main() {
6666+ let term = await createTerm({ width: 80, height: 24 });
6767+6868+ let { output, events } = term.render(
6969+ [
7070+ open("root", { layout: { width: grow(), height: grow(), direction: "ltr" } }),
7171+ open("sidebar", { layout: { width: fixed(20), height: grow() } }),
7272+ text("Sidebar"),
7373+ close(),
7474+ open("main", { layout: { width: grow(), height: grow() } }),
7575+ text("Main content"),
7676+ close(),
7777+ close(),
7878+ ],
7979+ { pointer: { x: 5, y: 2, down: false } },
8080+ );
8181+8282+ for (let event of events) {
8383+ // { type: "pointerenter", id: "sidebar" }
8484+ // { type: "pointerleave", id: "sidebar" }
8585+ // { type: "pointerclick", id: "main" }
8686+ }
8787+}
8888+```
8989+9090+### RenderResult
9191+9292+| Property | Type | Description |
9393+|---|---|---|
9494+| `output` | `Uint8Array` | ANSI bytes to write to stdout |
9595+| `events` | `PointerEvent[]` | Pointer enter/leave/click events |
9696+| `info` | `RenderInfo` | Element bounds lookup |
9797+| `errors` | `ClayError[]` | Layout errors from Clay |
9898+| `animating` | `boolean` | `true` when transitions are still running |
9999+100100+### RenderInfo.get(id)
101101+102102+Returns element bounds for a given id, or `undefined` if not found:
103103+104104+```ts twoslash
105105+import { close, createTerm, open, text, grow } from "@bomb.sh/tty";
106106+107107+async function main() {
108108+ let term = await createTerm({ width: 80, height: 24 });
109109+ let { info } = term.render([
110110+ open("root", { layout: { width: grow(), height: grow() } }),
111111+ text("Hello"),
112112+ close(),
113113+ ]);
114114+ let bounds = info.get("root")?.bounds;
115115+}
116116+```
117117+118118+### PointerEvent
119119+120120+| Type | Shape |
121121+|---|---|
122122+| `pointerenter` | `{ type: "pointerenter", id: string }` |
123123+| `pointerleave` | `{ type: "pointerleave", id: string }` |
124124+| `pointerclick` | `{ type: "pointerclick", id: string }` |
125125+126126+### ClayError
127127+128128+Layout errors include a `type` and `message`. Known error types:
129129+130130+- `TEXT_MEASUREMENT_FUNCTION_NOT_PROVIDED`
131131+- `ARENA_CAPACITY_EXCEEDED`
132132+- `ELEMENTS_CAPACITY_EXCEEDED`
133133+- `TEXT_MEASUREMENT_CAPACITY_EXCEEDED`
134134+- `DUPLICATE_ID`
135135+- `FLOATING_CONTAINER_PARENT_NOT_FOUND`
136136+- `PERCENTAGE_OVER_1`
137137+- `INTERNAL_ERROR`
138138+- `UNBALANCED_OPEN_CLOSE`
139139+- `CLIP_DEPTH_EXCEEDED`
140140+141141+## Frame loop with transitions
142142+143143+When using transitions, gate your render loop on `animating`:
144144+145145+```ts twoslash
146146+import { close, createTerm, open, text, grow } from "@bomb.sh/tty";
147147+148148+async function main() {
149149+ let term = await createTerm({ width: 80, height: 24 });
150150+151151+ function frame() {
152152+ let { output, animating } = term.render([
153153+ open("root", { layout: { width: grow(), height: grow() } }),
154154+ text("Animating..."),
155155+ close(),
156156+ ]);
157157+ process.stdout.write(output);
158158+ if (animating) setTimeout(frame, 16);
159159+ }
160160+161161+ frame();
162162+}
163163+```
164164+165165+<Aside type="note">
166166+Line mode (`mode: "line"`) renders into a region of the main screen scrollback instead of the alternate buffer. See the [inline regions example](https://github.com/bombshell-dev/tty/blob/main/examples/inline-regions/index.ts).
167167+</Aside>
168168+169169+## Next steps
170170+171171+- [Ops API](/docs/tty/api/ops) (building UI directives)
172172+- [Input API](/docs/tty/api/input) (parsing stdin events)
+76
src/content/docs/tty/api/termcodes.mdx
···11+---
22+title: Termcodes
33+description: Low-level ANSI escape sequence builders for @bomb.sh/tty
44+---
55+66+import { Aside } from '@astrojs/starlight/components';
77+88+The termcodes module provides low-level escape sequence builders. Each function returns a `Uint8Array` you can write directly to stdout.
99+1010+<Aside type="tip">
1111+Prefer the [settings helpers](/docs/tty/api/settings) for common terminal mode changes. Use termcodes when you need custom sequences or sequences not covered by settings.
1212+</Aside>
1313+1414+## ESC(str)
1515+1616+Encode a plain escape sequence. Prepends `ESC` (`\x1b`) to the given string.
1717+1818+```ts twoslash
1919+import { ESC } from "@bomb.sh/tty";
2020+2121+process.stdout.write(ESC("7")); // save cursor (DECSC)
2222+```
2323+2424+## CSI(str)
2525+2626+Encode a Control Sequence Introducer command. Prepends `ESC[` to the given string.
2727+2828+```ts twoslash
2929+import { CSI } from "@bomb.sh/tty";
3030+3131+process.stdout.write(CSI("2J")); // clear screen
3232+```
3333+3434+## DSR()
3535+3636+Request cursor position via Device Status Report. Sends `CSI 6n`. The terminal responds with a Cursor Position Report (`CSI row ; column R`) where row and column are 1-based.
3737+3838+```ts twoslash
3939+import { DSR } from "@bomb.sh/tty";
4040+4141+process.stdout.write(DSR());
4242+```
4343+4444+The response is parsed as a `cursor` event by the [input parser](/docs/tty/api/input). Used by the [inline regions example](https://github.com/bombshell-dev/tty/blob/main/examples/inline-regions/index.ts) to place animated output in scrollback.
4545+4646+## SHOWCURSOR() / HIDECURSOR()
4747+4848+Show or hide the cursor (DEC private mode 25, DECTCEM).
4949+5050+```ts twoslash
5151+import { HIDECURSOR, SHOWCURSOR } from "@bomb.sh/tty";
5252+5353+process.stdout.write(HIDECURSOR());
5454+process.stdout.write(SHOWCURSOR());
5555+```
5656+5757+Equivalent to [`cursor(false)`](/docs/tty/api/settings) / [`cursor(true)`](/docs/tty/api/settings).
5858+5959+## ALTSCREEN(options?) / MAINSCREEN()
6060+6161+Switch between main and alternate screen buffers.
6262+6363+```ts twoslash
6464+import { ALTSCREEN, MAINSCREEN } from "@bomb.sh/tty";
6565+6666+process.stdout.write(ALTSCREEN()); // enter, clear
6767+process.stdout.write(ALTSCREEN({ clear: false })); // enter, preserve
6868+process.stdout.write(MAINSCREEN()); // return to main screen
6969+```
7070+7171+Equivalent to [`alternateBuffer()`](/docs/tty/api/settings) / its revert sequence.
7272+7373+## Next steps
7474+7575+- [Settings API](/docs/tty/api/settings) (composable terminal mode helpers)
7676+- [Getting Started](/docs/tty/basics/getting-started)
+208
src/content/docs/tty/basics/getting-started.mdx
···11+---
22+title: Getting Started
33+description: Learn how to get started with @bomb.sh/tty
44+---
55+66+import { Tabs, TabItem, Aside } from '@astrojs/starlight/components';
77+88+`@bomb.sh/tty` is a low-level, platform-independent terminal renderer and event parser for JavaScript. Use it directly to build full-screen or inline terminal UIs, or as the foundation for your own framework.
99+1010+## Features
1111+1212+- **Declarative terminal UI:** Flexbox-like layout powered by [Clay](https://github.com/nicbarker/clay), with borders, colors, floating elements, scroll containers, and pointer hit-testing
1313+- **Zero I/O:** Never reads stdin or writes stdout. Feed bytes in, get bytes and events back
1414+- **Runs everywhere:** The entire engine is compiled to WebAssembly with no native dependencies for consumers
1515+1616+## When to use it
1717+1818+Reach for `@bomb.sh/tty` when you need a **rendering engine** for terminal UI: layout, styling, diffing, and input parsing, without committing to a particular app framework or I/O model.
1919+2020+### Good fits
2121+2222+- **Full-screen applications:** Dashboards, tools, and games that take over the alternate screen buffer with layouts, borders, mouse hover, and keyboard input (see the [2048](/docs/tty/guides/examples) and [keyboard](/docs/tty/guides/examples) demos)
2323+- **Inline animated output:** Spinners, progress bars, or live status regions embedded in normal scrollback without switching to a full-screen mode (see [inline regions](/docs/tty/guides/examples))
2424+- **Rich, structured output:** Diff views, panels, sidebars, and multi-pane layouts where hand-written ANSI becomes unmaintainable
2525+- **Interactive layouts with pointer support:** Hover states, clickable regions, and drag-style interactions driven by terminal mouse reporting
2626+- **Animated UI:** Transitions on width, color, position, and other properties with efficient cell-level diffing between frames
2727+- **Custom TUI frameworks:** Zero I/O means you wire stdin/stdout (or any byte stream) yourself; the engine stays a pure compute layer you can embed in Node, Deno, Bun, or the browser
2828+2929+### When to pick something else
3030+3131+- **One-shot CLI prompts** (text input, selects, confirms): use [`@clack/prompts`](/docs/clack/basics/getting-started); it targets a different problem and uses Node's built-in `tty` module, not this package
3232+- **Component-based UI with state and bindings:** use `@clack/ui`, which is built on `@bomb.sh/tty` and handles the higher-level framework concerns for you
3333+3434+## Requirements
3535+3636+Node >= 22. Also works in Deno, Bun, and browsers.
3737+3838+## Installation
3939+4040+<Tabs>
4141+ <TabItem label="npm" icon="seti:npm">
4242+ ```bash
4343+ npm install @bomb.sh/tty
4444+ ```
4545+ </TabItem>
4646+ <TabItem label="pnpm" icon="pnpm">
4747+ ```bash
4848+ pnpm add @bomb.sh/tty
4949+ ```
5050+ </TabItem>
5151+ <TabItem label="Yarn" icon="seti:yarn">
5252+ ```bash
5353+ yarn add @bomb.sh/tty
5454+ ```
5555+ </TabItem>
5656+</Tabs>
5757+5858+## Core concepts
5959+6060+**Frames as snapshots.** Each frame is a complete, independent UI description. Build an array of ops (`open`, `text`, `close`), pass it to `term.render()`, and write the returned ANSI bytes to stdout. The renderer carries layout and diff state between frames, not a persistent component tree.
6161+6262+**Zero I/O boundary.** You own stdin/stdout. `@bomb.sh/tty` is pure computation: one WASM call per frame on the output side, and a byte-stream parser on the input side.
6363+6464+**Efficient output.** Clay runs layout, walks render commands into a cell buffer, and diffs against the previous frame. Only changed cells produce ANSI escape sequences.
6565+6666+## Architecture
6767+6868+### Output
6969+7070+```mermaid
7171+flowchart LR
7272+ subgraph ts [TypeScript]
7373+ Ops["UI ops"]
7474+ Stdout["stdout.write"]
7575+ end
7676+ subgraph wasm [WASM]
7777+ Clay["Clay layout"]
7878+ Diff["Cell diff"]
7979+ Esc["Escape bytes"]
8080+ end
8181+ Ops -->|"Uint32Array"| Clay
8282+ Clay --> Diff
8383+ Diff --> Esc
8484+ Esc -->|"ANSI bytes"| Stdout
8585+```
8686+8787+### Input
8888+8989+```mermaid
9090+flowchart LR
9191+ subgraph ts [TypeScript]
9292+ Stdin["stdin.read"]
9393+ Events["InputEvent[]"]
9494+ end
9595+ subgraph wasm [WASM]
9696+ Parse["Trie match"]
9797+ Decode["UTF-8 / mouse / ESC"]
9898+ end
9999+ Stdin -->|"raw bytes"| Parse
100100+ Parse --> Decode
101101+ Decode --> Events
102102+```
103103+104104+Raw bytes are fed into a WASM parser that recognizes VT/ANSI escape sequences, UTF-8 codepoints, and mouse protocols. Partial sequences that arrive across read boundaries are reassembled automatically. A lone ESC byte is held for a configurable latency window (default 25ms) before being emitted.
105105+106106+## Quick start: rendering
107107+108108+To render this:
109109+110110+```
111111+╭───────────────╮
112112+│ Hello, World! │
113113+╰───────────────╯
114114+```
115115+116116+```ts twoslash
117117+import { close, createTerm, grow, open, rgba, text } from "@bomb.sh/tty";
118118+119119+async function main() {
120120+ let term = await createTerm({ width: 80, height: 24 });
121121+122122+ let { output } = term.render([
123123+ open("root", {
124124+ layout: { width: grow(), height: grow(), direction: "ttb" },
125125+ }),
126126+ open("greeting", {
127127+ layout: { padding: { left: 1, right: 1, top: 1, bottom: 1 } },
128128+ border: {
129129+ color: rgba(0, 255, 0),
130130+ left: 1, right: 1, top: 1, bottom: 1,
131131+ },
132132+ cornerRadius: { tl: 1, tr: 1, bl: 1, br: 1 },
133133+ }),
134134+ text("Hello, World!"),
135135+ close(),
136136+ close(),
137137+ ]);
138138+139139+ process.stdout.write(output);
140140+}
141141+```
142142+143143+## Quick start: input
144144+145145+```ts twoslash
146146+import { createInput } from "@bomb.sh/tty";
147147+148148+async function main() {
149149+ let input = await createInput({ escLatency: 25 });
150150+151151+ process.stdin.setRawMode(true);
152152+ let timer: ReturnType<typeof setTimeout> | undefined;
153153+154154+ process.stdin.on("data", (buf) => {
155155+ clearTimeout(timer);
156156+157157+ let { events, pending } = input.scan(new Uint8Array(buf));
158158+159159+ for (let event of events) {
160160+ console.log(event);
161161+ }
162162+163163+ if (pending) {
164164+ timer = setTimeout(() => {
165165+ let flush = input.scan();
166166+ for (let event of flush.events) {
167167+ console.log(event);
168168+ }
169169+ }, pending.delay);
170170+ }
171171+ });
172172+}
173173+```
174174+175175+## Quick start: terminal modes
176176+177177+Use composable settings to enter alternate buffer mode, hide the cursor, and enable mouse tracking:
178178+179179+```ts twoslash
180180+import {
181181+ alternateBuffer,
182182+ cursor,
183183+ mouseTracking,
184184+ settings,
185185+} from "@bomb.sh/tty";
186186+187187+let tty = settings(alternateBuffer(), cursor(false), mouseTracking());
188188+process.stdout.write(tty.apply);
189189+190190+// on exit:
191191+process.stdout.write(tty.revert);
192192+```
193193+194194+## Demos
195195+196196+The input parser decodes raw terminal bytes into structured events. Here you can see each key event as the string "hello world" is typed:
197197+198198+
199199+200200+Hover styles applied to UI elements in response to pointer state. Clay drives hit testing, so you don't need manual coordinate math:
201201+202202+
203203+204204+## Next steps
205205+206206+1. Explore [runnable examples](/docs/tty/guides/examples) on GitHub
207207+2. Read the [Ops API](/docs/tty/api/ops) for layout and styling directives
208208+3. Join our [Discord community](https://bomb.sh/chat) for support and discussions
+120
src/content/docs/tty/guides/examples.mdx
···11+---
22+title: Examples
33+description: Runnable @bomb.sh/tty demos and what to learn from each
44+---
55+66+import { Aside } from '@astrojs/starlight/components';
77+88+The best way to learn `@bomb.sh/tty` after the [getting started guide](/docs/tty/basics/getting-started) is to run the demos in the [tty repository](https://github.com/bombshell-dev/tty). Each one is a small app that shows how the pieces fit together: render loop, input parsing, terminal modes, transitions.
99+1010+<Aside type="note">
1111+Examples are **not** shipped in the npm package. Clone the repo to run them. They also require a **real terminal**. They won't run in the browser-based Clack WebContainer on this site.
1212+</Aside>
1313+1414+## Get set up
1515+1616+```bash
1717+git clone https://github.com/bombshell-dev/tty.git
1818+cd tty
1919+make # build the WASM bundle
2020+node examples/keyboard/index.ts
2121+```
2222+2323+<Aside type="note">
2424+Requires **Node >= 22** or **Deno**. Run all commands from the repository root (`tty/`), not from inside `examples/`.
2525+</Aside>
2626+2727+## Which demo should I run?
2828+2929+| Demo | Start here if you want to… | Key APIs |
3030+|---|---|---|
3131+| [Keyboard](#keyboard) | Wire up input, pointer hover, and terminal modes | [`createInput`](/docs/tty/api/input), [`settings`](/docs/tty/api/settings), [`render` pointer option](/docs/tty/api/term) |
3232+| [Transitions](#transitions) | Animate layout and color between frames | [`open` transitions](/docs/tty/api/ops), [`animating`](/docs/tty/api/term) |
3333+| [Inline regions](#inline-regions) | Draw animated output in scrollback, not full-screen | [`render` line mode](/docs/tty/api/term), [`DSR`](/docs/tty/api/termcodes) |
3434+| [2048](#2048) | See everything combined in a real app | ops, term, input, settings |
3535+| [Diff](#diff) | Highlight per-glyph in a layout | [`text` `bg`](/docs/tty/api/ops) |
3636+3737+**Suggested order:** keyboard → transitions → inline regions → 2048. Diff is a focused layout exercise you can skip until you need it.
3838+3939+## Keyboard
4040+4141+[`examples/keyboard/index.ts`](https://github.com/bombshell-dev/tty/blob/main/examples/keyboard/index.ts)
4242+4343+```bash
4444+node examples/keyboard/index.ts
4545+```
4646+4747+This is the best first demo. It shows the full loop most apps follow:
4848+4949+1. Apply [terminal settings](/docs/tty/api/settings) (alternate buffer, hidden cursor, mouse tracking)
5050+2. Create a [term](/docs/tty/api/term) and feed [pointer state](/docs/tty/api/term) each frame
5151+3. Parse stdin through [`createInput`](/docs/tty/api/input) and dispatch events
5252+4. Revert settings on exit
5353+5454+Type on the keyboard to see decoded key events. Move the mouse to see hover styles. Clay handles hit testing, so you don't need manual coordinate math.
5555+5656+
5757+5858+
5959+6060+## Transitions
6161+6262+[`examples/transitions/sidebar.ts`](https://github.com/bombshell-dev/tty/blob/main/examples/transitions/sidebar.ts) · [`examples/transitions/notecards.ts`](https://github.com/bombshell-dev/tty/blob/main/examples/transitions/notecards.ts)
6363+6464+```bash
6565+node examples/transitions/sidebar.ts
6666+node examples/transitions/notecards.ts
6767+```
6868+6969+Change layout or style properties between frames and attach a [`transition`](/docs/tty/api/ops) to elements. Gate your render loop on [`animating`](/docs/tty/api/term) so you only redraw while motion is in progress.
7070+7171+The sidebar demo animates width with keyboard shortcuts. Notecards interpolates layout and background color.
7272+7373+{/* TODO: add transitions-sidebar.gif (record `node examples/transitions/sidebar.ts`) */}
7474+{/* TODO: add transitions-notecards.gif (record `node examples/transitions/notecards.ts`) */}
7575+7676+## Inline regions
7777+7878+[`examples/inline-regions/index.ts`](https://github.com/bombshell-dev/tty/blob/main/examples/inline-regions/index.ts)
7979+8080+```bash
8181+node examples/inline-regions/index.ts
8282+```
8383+8484+Renders spinners, progress bars, and small animations into **normal scrollback** instead of taking over the screen. Uses [`DSR`](/docs/tty/api/termcodes) to query cursor position and [`render` in line mode](/docs/tty/api/term) to update a fixed region.
8585+8686+Reach for this pattern when you want live output inside a regular CLI workflow (build logs, install progress, status lines) without switching to the alternate buffer.
8787+8888+{/* TODO: add inline-regions.gif (record `node examples/inline-regions/index.ts`) */}
8989+9090+## 2048
9191+9292+[`examples/2048/index.ts`](https://github.com/bombshell-dev/tty/blob/main/examples/2048/index.ts)
9393+9494+```bash
9595+node examples/2048/index.ts
9696+```
9797+9898+A complete game combining transitions, keyboard focus, pointer clicks, live resize, and an fps counter. Good reference once you understand the basics.
9999+100100+**Controls:** arrows or `wasd` to move · `Tab`/`Shift+Tab` for focus · `Enter`/`Space` to activate · `n` new game · `u` undo · `q` or `Ctrl+C` to quit
101101+102102+{/* TODO: add 2048.gif (record `node examples/2048/index.ts`) */}
103103+104104+## Diff
105105+106106+[`examples/diff/index.ts`](https://github.com/bombshell-dev/tty/blob/main/examples/diff/index.ts)
107107+108108+```bash
109109+node examples/diff/index.ts
110110+```
111111+112112+A code-review diff view with per-glyph `bg` highlighting on [`text`](/docs/tty/api/ops) nodes. Useful if you're building structured, read-only output rather than interactive UI.
113113+114114+{/* TODO: add diff.gif (record `node examples/diff/index.ts`) */}
115115+116116+## Next steps
117117+118118+- [Getting Started](/docs/tty/basics/getting-started) (install, architecture, quick starts)
119119+- [Term API](/docs/tty/api/term) (render loop and pointer events)
120120+- [Input API](/docs/tty/api/input) (stdin event parsing)