Loading...
Loading...
Build terminal UIs with OpenTUI/React. Use when creating screens, components, handling keyboard input, managing scroll, or navigating between views. Covers JSX intrinsics, useKeyboard, scrollbox patterns, and state preservation.
npx skill4agent add ainergiz/xfeed opentui.context/repos/opentuibun run sync-contextimport { useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/react";
import type { ScrollBoxRenderable, KeyEvent } from "@opentui/core";// CORRECT - OpenTUI intrinsics
<box style={{ flexDirection: "column" }}>
<text fg="#ffffff">Hello</text>
<scrollbox ref={scrollRef} focused />
</box>
// WRONG - Not OpenTUI
<div>, <span>, <Box>, <Text>| Element | Purpose | Key Props |
|---|---|---|
| Container/layout | |
| Text content (strings only!) | |
| Scrollable container | |
| Hyperlink (OSC8) | |
| Text input | |
| Multi-line input | |
// WRONG - Cannot nest elements in <text>
<text>Hello <text fg="red">world</text></text>
// CORRECT - Use row box for inline styling
<box style={{ flexDirection: "row" }}>
<text>Hello </text>
<text fg="red">world</text>
</box>focuseduseKeyboard((key) => {
if (!focused) return; // MUST check first!
if (key.name === "j") moveDown();
});// WRONG - useEffect runs after render, scroll already reset
useEffect(() => { if (!focused) savedScroll.current = scrollRef.current?.scrollTop; }, [focused]);
// CORRECT - Save before state change
const handleSelect = () => {
savedScroll.current = scrollRef.current?.scrollTop; // Save first!
onSelect(item);
};<text>
Visit <a href="https://example.com">example.com</a> for more
</text>| Key | | Key | | |
|---|---|---|---|---|
| Enter | | Arrows | | |
| Escape | | Letters | | |
| Tab | | Shift+Letter | |
const scrollbox = scrollRef.current;
scrollbox.scrollTop // Current position
scrollbox.scrollHeight // Total content height
scrollbox.viewport.height // Visible area
scrollbox.scrollTo(pos) // Absolute scroll
scrollbox.scrollBy(delta) // Relative scroll
scrollbox.getChildren() // Find elements by ID// Full-height with fixed header/footer
<box style={{ flexDirection: "column", height: "100%" }}>
<box style={{ flexShrink: 0 }}>{/* Header */}</box>
<scrollbox style={{ flexGrow: 1 }}>{/* Content */}</scrollbox>
<box style={{ flexShrink: 0 }}>{/* Footer */}</box>
</box>
// Prevent unwanted spacing (Yoga quirk)
<box style={{ justifyContent: "flex-start", marginBottom: 0, paddingBottom: 0 }}>bun add --dev react-devtools-core@7
npx react-devtools@7 # Start standalone devtools
DEV=true bun run start # Run app with devtools enabledsrc/app.tsxsrc/components/PostList.tsxsrc/components/PostCard.tsxsrc/modals/FolderPicker.tsxsrc/hooks/useListNavigation.ts