Skip to main content 首页 创作者 brianlovin hn-cli opentui
opentui Build terminal UIs with OpenTUI, a TypeScript library using Yoga-powered flexbox layout. Use when creating terminal applications, working with Text/Box/Input/Select/ScrollBox components, handling keyboard input, or writing OpenTUI tests.
跳到安装 Skills Marketplace 发现并探索由社区构建的 Agent Skills
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/brianlovin/hn-cli --skill opentui命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
下载 Zip 下载中... name OpenTUI description Build terminal UIs with OpenTUI, a TypeScript library using Yoga-powered flexbox layout. Use when creating terminal applications, working with Text/Box/Input/Select/ScrollBox components, handling keyboard input, or writing OpenTUI tests.
OpenTUI Development Guide
OpenTUI is a TypeScript library for building terminal user interfaces. It requires Bun as the runtime.
Installation
bun add @opentui/core
Or with React bindings:
bun add @opentui/react @opentui/core react
Core Concepts
Two APIs: Renderables vs Constructs
Renderables - Direct class instantiation with full control:
const text = new TextRenderable (renderer, {
id : "greeting" ,
content : "Hello!" ,
fg : "#00FF00" ,
});
Constructs - Functional composition (simpler syntax):
Text ({ content : "Hello!" , fg : "#00FF00" })
Layout System
OpenTUI uses Yoga (flexbox) for layout. Key properties:
flexDirection: "row" | "column"
justifyContent: "flex-start" | "center" | "flex-end" | "space-between" | "space-around"
: "flex-start" | "center" | "flex-end" | "stretch"
alignItems
width / height: number or percentage string ("50%")
padding / gap: spacing in cells
flexGrow: number for proportional sizing
Colors Use hex strings ("#FF0000") or RGBA values ({ r: 255, g: 0, b: 0, a: 255 }).
Components
Text Display styled text with colors and attributes.
import { Text , t, bold, fg, italic } from "@opentui/core" ;
Text ({ content : "Hello" , fg : "#00FF00" })
Text ({ content : t`${bold(fg("#FFFF00" )("bold yellow" ))} and ${italic("italic" )} ` })
Text Attributes : BOLD, DIM, ITALIC, UNDERLINE, BLINK, INVERSE, HIDDEN, STRIKETHROUGH (combine with bitwise OR)
Box Container with borders and layout.
import { Box } from "@opentui/core" ;
Box ({
border : "rounded" ,
title : "Panel Title" ,
titleAlign : "center" ,
padding : 1 ,
flexDirection : "column" ,
gap : 1 ,
},
Text ({ content : "Child 1" }),
Text ({ content : "Child 2" })
)
Input Text input field requiring focus.
import { InputRenderable , InputEvent } from "@opentui/core" ;
const input = new InputRenderable (renderer, {
width : 30 ,
placeholder : "Type here..." ,
backgroundColor : "#333333" ,
focusedBackgroundColor : "#444444" ,
});
input.on (InputEvent .INPUT , () => console .log (input.value ));
input.on (InputEvent .ENTER , () => handleSubmit (input.value ));
Select Vertical selection list requiring focus.
import { SelectRenderable , SelectEvent } from "@opentui/core" ;
const select = new SelectRenderable (renderer, {
options : [
{ name : "Option 1" , description : "First option" , value : "opt1" },
{ name : "Option 2" , description : "Second option" , value : "opt2" },
],
wrapSelection : true ,
});
select.on (SelectEvent .ITEM_SELECTED , (index, option ) => {
console .log (`Selected: ${option.value} ` );
});
Keyboard : j/Down (next), k/Up (prev), Shift+Up/Down (fast scroll), Enter (select)
ScrollBox Scrollable container for long content.
import { ScrollBoxRenderable } from "@opentui/core" ;
const scrollbox = new ScrollBoxRenderable (renderer, {
scrollY : true ,
stickyScroll : "bottom" ,
viewportCulling : true ,
});
scrollbox.scrollBy ({ y : 10 , unit : "line" });
scrollbox.scrollTo ({ x : 0 , y : 100 });
Keyboard (when focused): Arrow keys (line), Page Up/Down (viewport), Home/End (boundaries)
Code Syntax-highlighted code display with Tree-sitter.
import { CodeRenderable , SyntaxStyle } from "@opentui/core" ;
const code = new CodeRenderable (renderer, {
content : 'console.log("Hello");' ,
filetype : "typescript" ,
syntaxStyle : SyntaxStyle .fromStyles ({ keyword : { fg : "#FF79C6" } }),
streaming : true ,
selectable : true ,
});
React Integration
import { createCliRenderer } from "@opentui/core" ;
import { createRoot } from "@opentui/react" ;
import { useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/react" ;
const renderer = createCliRenderer ();
const root = createRoot (renderer);
function App ( ) {
const { width, height } = useTerminalDimensions ();
useKeyboard ((event ) => {
if (event.key === "q" ) process.exit (0 );
});
return <box border ="rounded" > <text > Terminal: {width}x{height}</text > </box > ;
}
root.render (<App /> );
Available hooks : useRenderer(), useKeyboard(), useOnResize(), useTerminalDimensions(), useTimeline()
Testing Use @opentui/core/testing for headless testing:
import { describe, it, expect, beforeEach, afterEach } from "bun:test" ;
import { createTestRenderer } from "@opentui/core/testing" ;
describe ("MyApp" , () => {
let renderer, mockInput, mockMouse, renderOnce, captureCharFrame;
beforeEach (async () => {
const ctx = await createTestRenderer ({
width : 80 ,
height : 24 ,
kittyKeyboard : true ,
});
renderer = ctx.renderer ;
mockInput = ctx.mockInput ;
mockMouse = ctx.mockMouse ;
renderOnce = ctx.renderOnce ;
captureCharFrame = ctx.captureCharFrame ;
});
afterEach (async () => {
await renderer.idle ();
renderer.destroy ();
});
it ("should handle keyboard input" , async () => {
await renderOnce ();
mockInput.pressKey ("j" );
await renderOnce ();
const frame = captureCharFrame ();
expect (frame).toContain ("expected text" );
});
it ("should handle mouse clicks" , async () => {
await mockMouse.click (10 , 5 );
await renderOnce ();
});
it ("should handle modifier keys" , async () => {
mockInput.pressKey ("j" , { super : true });
mockInput.pressKey ("k" , { ctrl : true });
mockInput.pressEnter ();
await renderOnce ();
});
});
Best Practices
Use await renderOnce() after state changes to ensure rendering completes before assertions
Use renderer.idle() in afterEach to wait for pending async operations
Structure components hierarchically - Box containers for layout, Text for content
Manage focus explicitly - Input and Select require focus to receive keyboard input
Use sticky scroll for chat-like interfaces where new content appears at bottom
Enable viewport culling on ScrollBox with many children for performance
Prefer Renderables for stateful components needing direct control, Constructs for simple composition
Documentation Links