{"id":50684839,"url":"https://github.com/bombshell-dev/clayterm","last_synced_at":"2026-06-08T22:01:43.427Z","repository":{"id":344870305,"uuid":"1182910315","full_name":"bombshell-dev/clayterm","owner":"bombshell-dev","description":"Platform independent 2D layout engine for terminal applications based on Clay","archived":false,"fork":false,"pushed_at":"2026-06-06T10:19:13.000Z","size":2489,"stargazers_count":18,"open_issues_count":44,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-06-06T12:11:46.358Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bombshell-dev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null},"funding":{"open_collective":"bombshell-dev"}},"created_at":"2026-03-16T04:37:34.000Z","updated_at":"2026-06-05T19:48:09.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/bombshell-dev/clayterm","commit_stats":null,"previous_names":["cowboyd/clayterm","bombshell-dev/clayterm"],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/bombshell-dev/clayterm","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bombshell-dev%2Fclayterm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bombshell-dev%2Fclayterm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bombshell-dev%2Fclayterm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bombshell-dev%2Fclayterm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bombshell-dev","download_url":"https://codeload.github.com/bombshell-dev/clayterm/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bombshell-dev%2Fclayterm/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34082130,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-08T02:00:07.615Z","response_time":111,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-08T22:01:41.092Z","updated_at":"2026-06-08T22:01:43.417Z","avatar_url":"https://github.com/bombshell-dev.png","language":"C","funding_links":["https://opencollective.com/bombshell-dev"],"categories":[],"sub_categories":[],"readme":"# clayterm\n\nA low-level, platform-independent terminal renderer and event parser for\nJavaScript. You can use clayterm directly, or as the foundation for your own\nframework.\n\n## Features\n\n**Declarative terminal UI** — Build terminal interfaces the same way you'd build\na web page. Clayterm uses [Clay](https://github.com/nicbarker/clay) under the\nhood, giving you flexbox-like layout, pointer detection, and scroll containers —\nall rendered to the terminal as box-drawing characters and ANSI escape\nsequences.\n\n**Zero I/O** — Clayterm never reads stdin or writes stdout. You feed it bytes\nand get bytes back. This makes it trivially embeddable in any framework, any\nruntime, any event loop. There are no opinions about how you do I/O, just pure\ncomputation.\n\n**Runs everywhere** — The entire engine is compiled to WebAssembly, so clayterm\nwill run anywhere JavaScript runs with no native dependencies, and no build step\nfor consumers.\n\n### Examples\n\nSee this keyboard example and more in the [examples folder](examples/README.md).\nThis demo uses Clayterm for all layout and input parsing.\n\n#### Keyboard Events\n\nThe input parser decodes raw terminal bytes into structured events. Here you can\nsee each key event as the string \"hello world\" is typed.\n\n![Keyboard events demo](examples/keyboard/keyboard-key-events.gif)\n\n#### Pointer Events\n\nHere we see hover styles applied to UI elements in response to the pointer\nstate. Clay drives the hit testing; no manual coordinate math required.\n\n![Pointer events demo](examples/keyboard/keyboard-pointer-events.gif)\n\n## Architecture\n\nClayterm does not do any I/O itself. On the ouput side, it converts UI elements\ninto a raw sequence of bytes and pointer events, and on the input side, it\nconverts a stream of raw bytes into structured events.\n\n### Output\n\nWith every frame, the entire UI tree is packed into a flat byte array and sent\nto WASM in a single call. On the C side, Clay runs layout, render commands are\nwalked into a cell buffer, and the buffer is diffed against the previous frame.\nOnly the cells that actually changed produce output. The result is an ANSI\nescape sequence that can be written directly to stdout. One trip to WASM per\nframe, double buffered, and only the bytes that need to change hit the output\nstream.\n\nBecause the WASM module is pure computation with no I/O, it runs anywhere\nWebAssembly does: Deno, Node, Bun, browsers, or any other runtime.\n\n```\n TypeScript                        WASM (C)\n+---------------+                +---------------------------+\n|               |  Uint32Array   |                           |\n| UI ops...     | =============\u003e | Clay layout               |\n|               |                |   -\u003e render commands      |\n+---------------+                |   -\u003e cell buffer (back)   |\n                                 |   -\u003e diff against (front) |\n                                 |   -\u003e escape bytes         |\n+---------------+                |                           |\n|               | ANSI byte array|                           |\n| stdout.write  | \u003c============= |                           |\n|               |                |                           |\n+---------------+                +---------------------------+\n```\n\n### Input\n\nRaw bytes from stdin are fed into a WASM-based parser that recognizes VT/ANSI\nescape sequences, UTF-8 codepoints, and mouse protocols (VT200, SGR, urxvt). The\nparser maintains its own internal buffer so partial sequences that arrive across\nread boundaries are reassembled automatically. A lone ESC byte is held for a\nconfigurable latency window (default 25ms) before being emitted, giving\nmulti-byte sequences time to arrive.\n\n```\n TypeScript                        WASM (C)\n+---------------+                +---------------------------+\n|               |  raw byte array|                           |\n| stdin.read    | =============\u003e | trie match (keys/seqs)    |\n|               |                |   -\u003e mouse protocol       |\n|               |                |   -\u003e UTF-8 decode         |\n+---------------+                |   -\u003e ESC codes            |\n                                 |                           |\n+---------------+                |                           |\n|               |  events[]      |                           |\n| KeyEvent      | \u003c============= |                           |\n| MouseDownEvent|                |                           |\n| MouseUpEvent  |                +---------------------------+\n| MouseMoveEvent|\n| WheelEvent    |\n| ResizeEvent   |\n+---------------+\n```\n\n## Usage\n\n### Rendering\n\nTo render this:\n\n```\n╭───────────────╮\n│ Hello, World! │\n╰───────────────╯\n```\n\n```typescript\nimport { close, createTerm, grow, open, rgba, text } from \"clayterm\";\n\nlet term = await createTerm({ width: 80, height: 24 });\n\nlet { output } = term.render([\n  open(\"root\", {\n    layout: { width: grow(), height: grow(), direction: \"ttb\" },\n  }),\n  open(\"greeting\", {\n    layout: { padding: { left: 1, right: 1 } },\n    border: {\n      color: rgba(0, 255, 0),\n      left: 1,\n      right: 1,\n      top: 1,\n      bottom: 1,\n    },\n    cornerRadius: { tl: 1, tr: 1, bl: 1, br: 1 },\n  }),\n  text(\"Hello, World!\"),\n  close(),\n  close(),\n]);\n\nprocess.stdout.write(output);\n```\n\n### Pointer detection\n\nPass pointer state to `render()` to have clayterm do hit detection and return\npointer events in addition to the byte sequence.\n\n```typescript\nlet { output, events } = term.render(\n  [\n    open(\"root\", {\n      layout: { width: grow(), height: grow(), direction: \"ltr\" },\n    }),\n    open(\"sidebar\", {\n      layout: { width: fixed(20), height: grow() },\n      bg: rgba(30, 30, 40),\n    }),\n    text(\"Sidebar\"),\n    close(),\n    open(\"main\", {\n      layout: { width: grow(), height: grow() },\n    }),\n    text(\"Main content\"),\n    close(),\n    close(),\n  ],\n  {\n    pointer: { x: mouseX, y: mouseY, down: mouseDown },\n  },\n);\n\nfor (let event of events) {\n  // { type: \"pointerenter\", id: \"sidebar\" }\n  // { type: \"pointerleave\", id: \"sidebar\" }\n  // { type: \"pointerclick\", id: \"main\" }\n  console.log(event);\n}\n\nprocess.stdout.write(output);\n```\n\n### Input parsing\n\n```typescript\nimport { createInput } from \"clayterm/input\";\n\nlet input = await createInput({ escLatency: 25 });\n\nprocess.stdin.setRawMode(true);\nlet timer: ReturnType\u003ctypeof setTimeout\u003e | undefined;\n\nprocess.stdin.on(\"data\", (buf) =\u003e {\n  clearTimeout(timer);\n\n  let { events, pending } = input.scan(new Uint8Array(buf));\n\n  for (let event of events) {\n    dispatch(event);\n  }\n\n  // if a lone ESC is pending, wait and re-scan to flush it\n  if (pending) {\n    timer = setTimeout(() =\u003e {\n      let flush = input.scan();\n      for (let event of flush.events) {\n        dispatch(event);\n      }\n    }, pending.delay);\n  }\n});\n```\n\n## Development\n\nFor local source builds, toolchain setup, and `clay` submodule instructions, see\n[BUILD.md](BUILD.md).\n\nQuick local validation:\n\n```sh\nmake\ndeno task test\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbombshell-dev%2Fclayterm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbombshell-dev%2Fclayterm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbombshell-dev%2Fclayterm/lists"}