[READ-ONLY] Mirror of https://github.com/bombshell-dev/tty. Platform independent 2D layout engine for terminal applications based on Clay
5

Configure Feed

Select the types of activity you want to include in your feed.

📃update README

Charles Lowell (Mar 30, 2026, 5:05 PM -0500) 10c686f7 f0b508b5

+97 -18
+97 -18
README.md
··· 1 1 # clayterm 2 2 3 - A terminal rendering backend for [Clay](https://github.com/nicbarker/clay), and 4 - a terminal input event parser compiled to WebAssembly. 3 + A low-level, platform-independent terminal renderer and event parser for JavaScript. You can use clayterm directly, or as the foundation for your own framework. 4 + 5 + ## Features 6 + 7 + **Declarative terminal UI** — Build terminal interfaces the same way you'd 8 + build a web page. Clayterm uses [Clay](https://github.com/nicbarker/clay) under 9 + the hood, giving you flexbox-like layout, pointer detection, and scroll 10 + containers — all rendered to the terminal as box-drawing characters and ANSI 11 + escape sequences. 12 + 13 + **Zero I/O** — Clayterm never reads stdin or writes stdout. You feed it bytes 14 + and get bytes back. This makes it trivially embeddable in any framework, any 15 + runtime, any event loop. There are no opinions about how you do I/O, just pure 16 + computation. 17 + 18 + **Runs everywhere** — The entire engine is compiled to WebAssembly, so 19 + clayterm will run anywhere JavaScript runs with no native 20 + dependencies, and no build step for consumers. 21 + 22 + ### Demo 23 + 24 + The application in this demo uses Clayterm for all layout and input parsing 25 + 26 + #### Keyboard Events 27 + 28 + The input parser decodes raw terminal bytes into structured events. 29 + Here you can see each key event as the string "hello world" is typed. 30 + 31 + ![Keyboard events demo](demo/keyboard-key-events.gif) 32 + 33 + #### Pointer Events 34 + 35 + Here we see hover styles applied to UI elements in response to the 36 + pointer state. Clay drives the hit testing; no manual coordinate math 37 + required. 38 + 39 + ![Pointer events demo](demo/keyboard-pointer-events.gif) 40 + 5 41 6 42 ## Architecture 43 + 44 + Clayterm does not do any I/O itself. On the ouput side, it converts UI elements into a raw sequence of bytes and pointer events, and on the input side, it converts a stream of raw bytes into structured events. 7 45 8 46 ### Output 9 47 ··· 54 92 | | 55 93 +---------------+ | | 56 94 | | events[] | | 57 - | CharEvent | <============= | | 58 - | KeyEvent | | | 59 - | MouseEvent | +---------------------------+ 60 - | DragEvent | 95 + | KeyEvent | <============= | | 96 + | MouseDownEvent| | | 97 + | MouseUpEvent | +---------------------------+ 98 + | MouseMoveEvent| 61 99 | WheelEvent | 62 100 | ResizeEvent | 63 101 +---------------+ ··· 65 103 66 104 ## Usage 67 105 68 - ### Output 106 + ### Rendering 107 + 108 + To render this: 109 + 110 + ``` 111 + ╭───────────────╮ 112 + │ Hello, World! │ 113 + ╰───────────────╯ 114 + ``` 69 115 70 116 ```typescript 71 117 import { close, createTerm, grow, open, rgba, text } from "clayterm"; 72 118 73 - const term = await createTerm({ width: 80, height: 24 }); 119 + let term = await createTerm({ width: 80, height: 24 }); 74 120 75 - const ansi = term.render([ 121 + let { output } = term.render([ 76 122 open("root", { 77 123 layout: { width: grow(), height: grow(), direction: "ttb" }, 78 124 }), 79 - open("box", { 80 - layout: { padding: { left: 2, top: 1 } }, 125 + open("greeting", { 126 + layout: { padding: { left: 1, right: 1 } }, 81 127 border: { 82 128 color: rgba(0, 255, 0), 83 - left: 1, 84 - right: 1, 85 - top: 1, 86 - bottom: 1, 129 + left: 1, right: 1, top: 1, bottom: 1, 87 130 }, 88 131 cornerRadius: { tl: 1, tr: 1, bl: 1, br: 1 }, 89 132 }), ··· 92 135 close(), 93 136 ]); 94 137 95 - process.stdout.write(ansi); 138 + process.stdout.write(output); 139 + ``` 140 + 141 + ### Pointer detection 142 + 143 + Pass pointer state to `render()` to have clayterm do hit detection and return 144 + pointer events in addition to the byte sequence. 145 + 146 + ```typescript 147 + let { output, events } = term.render([ 148 + open("root", { 149 + layout: { width: grow(), height: grow(), direction: "ltr" }, 150 + }), 151 + open("sidebar", { 152 + layout: { width: fixed(20), height: grow() }, 153 + bg: rgba(30, 30, 40), 154 + }), 155 + text("Sidebar"), 156 + close(), 157 + open("main", { 158 + layout: { width: grow(), height: grow() }, 159 + }), 160 + text("Main content"), 161 + close(), 162 + close(), 163 + ], { 164 + pointer: { x: mouseX, y: mouseY, down: mouseDown }, 165 + }); 166 + 167 + for (let event of events) { 168 + // { type: "pointerenter", id: "sidebar" } 169 + // { type: "pointerleave", id: "sidebar" } 170 + // { type: "pointerclick", id: "main" } 171 + console.log(event); 172 + } 173 + 174 + process.stdout.write(output); 96 175 ``` 97 176 98 - ### Input 177 + ### Input parsing 99 178 100 179 ```typescript 101 180 import { createInput } from "clayterm/input"; 102 181 103 - const input = await createInput({ escLatency: 25 }); 182 + let input = await createInput({ escLatency: 25 }); 104 183 105 184 process.stdin.setRawMode(true); 106 185 let timer: ReturnType<typeof setTimeout> | undefined;
demo/keyboard-key-events.gif

This is a binary file and will not be displayed.

demo/keyboard-pointer-events.gif

This is a binary file and will not be displayed.