opentui-dev
Build and modify terminal user interfaces using OpenTUI with React or Core API. Use when implementing terminal UIs, TUIs, CLI applications, interactive terminal components, keyboard navigation, terminal styling, or working on OpenTUI-based applications.
Works with
---
name: opentui-dev
description: Build and modify terminal user interfaces using OpenTUI with React or Core API. Use when implementing terminal UIs, TUIs, CLI applications, interactive terminal components, keyboard navigation, terminal styling, or working on OpenTUI-based applications.
license: MIT
---
# OpenTUI Development Skill
This skill provides comprehensive guidance for building terminal user interfaces (TUIs) using OpenTUI, a TypeScript library for creating component-based terminal applications with React or Core API.
## When to use this skill
Use this skill when:
- Building a new terminal user interface (TUI) or CLI application
- Working with OpenTUI-based projects
- Creating interactive terminal components (inputs, selects, lists, etc.)
- Implementing keyboard navigation and event handling in terminals
- Adding syntax highlighting or code display in terminal UIs
- Creating diff viewers, text editors, or terminal-based forms
- Styling terminal applications with borders, colors, and flexbox layouts
- Debugging or enhancing OpenTUI applications
- User mentions: TUI, terminal UI, CLI app, OpenTUI, terminal components
## What is OpenTUI
OpenTUI is a TypeScript framework for building terminal user interfaces with:
- **Component-based architecture**: Similar to web frameworks, compose UIs from reusable components
- **React integration**: Use familiar React patterns with JSX for declarative terminal UIs
- **Flexbox layouts**: CSS Flexbox-like layout system powered by Yoga layout engine
- **Rich components**: Built-in inputs, selects, code viewers, diff viewers, and more
- **Type-safe**: Full TypeScript support with strict mode
- **High performance**: Native Zig rendering layer for optimal performance
### Core Packages
- `@opentui/core` - Core library with imperative API and primitives (standalone)
- `@opentui/react` - React reconciler for declarative UI development
- `@opentui/solid` - SolidJS reconciler (alternative framework)
## Quick Start
### Creating a new OpenTUI React project
```bash
bun create tui --template react
cd my-tui-app
bun install
bun run index.tsx
```
### Manual installation
```bash
bun install @opentui/core @opentui/react react
```
### Basic "Hello World" example
```tsx
import { createCliRenderer } from "@opentui/core"
import { createRoot } from "@opentui/react"
function App() {
return (
<box style={{ padding: 2 }}>
<text fg="#00FF00">Hello, OpenTUI!</text>
</box>
)
}
const renderer = await createCliRenderer({ useAlternateScreen: true })
createRoot(renderer).render(<App />)
renderer.start()
```
## TypeScript Configuration
OpenTUI requires specific TypeScript settings:
```json
{
"compilerOptions": {
"lib": ["ESNext", "DOM"],
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"jsxImportSource": "@opentui/react",
"strict": true,
"skipLibCheck": true
}
}
```
Key settings:
- `jsxImportSource: "@opentui/react"` - Use OpenTUI's JSX factory
- `moduleResolution: "bundler"` - Required for Bun
- `strict: true` - Enable strict type checking
## Core Concepts
### 1. Renderer
The renderer manages terminal output, input events, and the rendering loop.
```typescript
import { createCliRenderer } from "@opentui/core"
const renderer = await createCliRenderer({
exitOnCtrlC: true, // Exit on Ctrl+C (default: true)
targetFps: 30, // Target frame rate
maxFps: 60, // Maximum frame rate
useAlternateScreen: true, // Use alternate screen (full-screen)
useMouse: true, // Enable mouse support
backgroundColor: "#000000", // Background color
useConsole: true, // Enable console overlay
})
// Start the render loop
renderer.start()
```
**Rendering Modes:**
- **Live Mode**: Call `renderer.start()` for continuous rendering at target FPS
- **On-Demand**: Without `start()`, renders only when changes occur
### 2. Layout System (Flexbox)
OpenTUI uses Yoga for CSS Flexbox-like layouts:
```tsx
<box
flexDirection="row" // row | column | row-reverse | column-reverse
justifyContent="center" // flex-start | flex-end | center | space-between | space-around
alignItems="center" // flex-start | flex-end | center | stretch | baseline
gap={2} // Gap between children
flexGrow={1} // Grow factor
flexShrink={1} // Shrink factor
flexBasis="auto" // Base size
flexWrap="wrap" // no-wrap | wrap | wrap-reverse
>
{/* Children */}
</box>
```
**Size Properties:**
```tsx
width={40} // Fixed width in characters
height={10} // Fixed height in lines
width="50%" // Percentage of parent
width="auto" // Auto-size based on content
minWidth={20} // Minimum width
maxWidth={80} // Maximum width
```
**Spacing:**
```tsx
padding={2} // All sides
paddingLeft={1} // Individual sides
margin={1} // Margin (all sides)
marginTop={2} // Individual margins
```
**Position:**
```tsx
position="relative" // relative | absolute
top={5} // Position from top
left={10} // Position from left
zIndex={999} // Stack order
```
### 3. Colors
Colors in OpenTUI use RGBA format or hex strings:
```tsx
import { RGBA } from "@opentui/core"
// Hex strings (most common)
<text fg="#FFFFFF" bg="#000000">Text</text>
// RGBA objects
const red = RGBA.fromHex("#FF0000")
const blue = RGBA.fromInts(0, 0, 255, 255) // RGB 0-255
const transparent = RGBA.fromValues(1.0, 1.0, 1.0, 0.5) // RGBA 0.0-1.0
```
### 4. Text Attributes
Style text with bold, italic, underline, etc.:
```tsx
import { TextAttributes } from "@opentui/core"
<text attributes={TextAttributes.BOLD}>Bold text</text>
<text attributes={TextAttributes.BOLD | TextAttributes.ITALIC}>Bold italic</text>
// Available attributes:
// BOLD, DIM, ITALIC, UNDERLINE, BLINK, INVERSE, HIDDEN, STRIKETHROUGH
```
## React Components
### Box Component
Container with borders, backgrounds, and layout:
```tsx
<box
title="Panel Title"
border // Show border
borderStyle="single" // single | double | rounded | bold | classic
borderColor="#FFFFFF"
backgroundColor="#1a1a1a"
focused={true} // Highlight when focused
padding={2}
flexDirection="column"
width={40}
height={10}
>
<text>Content</text>
</box>
```
**Border Styles:**
- `single` - Single line (─│┌┐└┘)
- `double` - Double line (═║╔╗╚╝)
- `rounded` - Rounded corners (─│╭╮╰╯)
- `bold` - Bold/thick line
- `classic` - ASCII style (+-|)
### Text Component
Display text with styling:
```tsx
// Simple text
<text fg="#FFFFFF" bg="#000000">Simple text</text>
// Rich text with children
<text>
<span fg="#FF0000">Red text</span>
{" "}
<strong>Bold</strong>
{" "}
<em>Italic</em>
{" "}
<u>Underlined</u>
<br />
<a href="https://example.com">Link</a>
</text>
// Formatted content
<text content="Preformatted content" fg="#00FF00" />
```
### Input Component
Single-line text input:
```tsx
const [value, setValue] = useState("")
<input
placeholder="Type here..."
value={value}
focused={true} // Must be focused to receive input
onInput={setValue} // Called on every keystroke
onSubmit={(val) => { // Called on Enter key
console.log("Submitted:", val)
}}
style={{
backgroundColor: "#1a1a1a",
focusedBackgroundColor: "#2a2a2a",
}}
/>
```
### Textarea Component
Multi-line text editor:
```tsx
import { useRef } from "react"
import type { TextareaRenderable } from "@opentui/core"
const textareaRef = useRef<TextareaRenderable>(null)
<textarea
ref={textareaRef}
placeholder="Type here..."
focused={true}
initialValue="Initial text"
style={{ width: 60, height: 10 }}
/>
// Access value: textareaRef.current?.getText()
```
### Select Component
List selection with scrolling:
```tsx
import type { SelectOption } from "@opentui/core"
const options: SelectOption[] = [
{ name: "Option 1", description: "First option", value: "opt1" },
{ name: "Option 2", description: "Second option", value: "opt2" },
{ name: "Option 3", description: "Third option", value: "opt3" },
]
<select
options={options}
focused={true}
onChange={(index, option) => {
console.log(`Selected ${index}:`, option)
}}
showScrollIndicator
style={{ height: 10 }}
/>
```
**Navigation:**
- Up/Down arrows - Move selection
- Enter - Confirm selection
- Auto-scrolls to keep selection visible
### Tab Select Component
Horizontal tab selection:
```tsx
<tab-select
options={[
{ name: "Home", description: "Dashboard view" },
{ name: "Settings", description: "Configuration" },
{ name: "Help", description: "Documentation" },
]}
focused={true}
onChange={(index, option) => {
console.log("Tab changed:", option.name)
}}
tabWidth={20}
/>
```
### Scrollbox Component
Scrollable container for long content:
```tsx
<scrollbox
focused={true}
style={{
height: 20,
rootOptions: { backgroundColor: "#24283b" },
wrapperOptions: { backgroundColor: "#1f2335" },
viewportOptions: { backgroundColor: "#1a1b26" },
contentOptions: { backgroundColor: "#16161e" },
scrollbarOptions: {
showArrows: true,
trackOptions: {
foregroundColor: "#7aa2f7",
backgroundColor: "#414868",
},
},
}}
>
{/* Long scrollable content */}
<text>Line 1</text>
<text>Line 2</text>
{/* ... many more lines */}
</scrollbox>
```
### Code Component
Display code with syntax highlighting:
```tsx
import { SyntaxStyle, RGBA } from "@opentui/core"
const syntaxStyle = SyntaxStyle.fromStyles({
keyword: { fg: RGBA.fromHex("#ff6b6b"), bold: true },
string: { fg: RGBA.fromHex("#51cf66") },
comment: { fg: RGBA.fromHex("#868e96"), italic: true },
function: { fg: RGBA.fromHex("#4dabf7") },
number: { fg: RGBA.fromHex("#ffd43b") },
})
<code
content={codeString}
filetype="typescript" // javascript, python, rust, go, etc.
syntaxStyle={syntaxStyle}
/>
```
### Line Number Component
Code with line numbers and diff highlights:
```tsx
import { useRef, useEffect } from "react"
import type { LineNumberRenderable } from "@opentui/core"
const lineNumberRef = useRef<LineNumberRenderable>(null)
useEffect(() => {
// Highlight added line
lineNumberRef.current?.setLineColor(1, "#1a4d1a")
lineNumberRef.current?.setLineSign(1, { after: " +", afterColor: "#22c55e" })
// Highlight deleted line
lineNumberRef.current?.setLineColor(5, "#4d1a1a")
lineNumberRef.current?.setLineSign(5, { after: " -", afterColor: "#ef4444" })
// Add diagnostic warning
lineNumberRef.current?.setLineSign(10, { before: "⚠️", beforeColor: "#f59e0b" })
}, [])
<line-number
ref={lineNumberRef}
fg="#6b7280"
bg="#161b22"
minWidth={3}
showLineNumbers={true}
>
<code content={codeContent} filetype="typescript" />
</line-number>
```
### Diff Component
Unified or split diff viewer:
```tsx
<diff
oldContent={oldCode}
newContent={newCode}
filetype="typescript"
syntaxStyle={syntaxStyle}
viewMode="unified" // unified | split
/>
```
### ASCII Font Component
Display ASCII art text:
```tsx
<ascii-font
text="OPENTUI"
font="block" // tiny | block | slick | shade
color={RGBA.fromHex("#00FF00")}
/>
```
## React Hooks
### useRenderer()
Access the renderer instance:
```tsx
import { useRenderer } from "@opentui/react"
const renderer = useRenderer()
// Show/hide console overlay
renderer.console.toggle()
renderer.console.show()
renderer.console.hide()
```
### useKeyboard(handler, options?)
Handle keyboard events:
```tsx
import { useKeyboard } from "@opentui/react"
import type { KeyEvent } from "@opentui/core"
useKeyboard((key: KeyEvent) => {
console.log("Key:", key.name)
console.log("Modifiers:", { ctrl: key.ctrl, shift: key.shift, alt: key.meta })
if (key.name === "escape") {
process.exit(0)
}
if (key.ctrl && key.name === "s") {
// Save action
}
}, { release: false }) // Set release: true for key release events
```
**Key Event Properties:**
- `name` - Key name (e.g., "a", "enter", "escape", "up", "down")
- `ctrl` - Ctrl key pressed
- `shift` - Shift key pressed
- `meta` - Alt/Meta key pressed (Linux/Windows)
- `option` - Option key pressed (macOS)
- `sequence` - Raw key sequence
### useTerminalDimensions()
Get current terminal size (auto-updates on resize):
```tsx
import { useTerminalDimensions } from "@opentui/react"
const { width, height } = useTerminalDimensions()
return (
<box style={{ width, height }}>
<text>{`Terminal: ${width}x${height}`}</text>
</box>
)
```
### useOnResize(callback)
Handle terminal resize events:
```tsx
import { useOnResize } from "@opentui/react"
useOnResize((width, height) => {
console.log(`Resized to ${width}x${height}`)
})
```
### useTimeline(options?)
Create animations:
```tsx
import { useTimeline } from "@opentui/react"
import { useEffect, useState } from "react"
const [width, setWidth] = useState(0)
const timeline = useTimeline({
duration: 2000,
loop: false,
autoplay: true,
})
useEffect(() => {
timeline.add(
{ width },
{
width: 50,
duration: 2000,
ease: "linear",
onUpdate: (animation) => {
setWidth(animation.targets[0].width)
},
}
)
}, [])
return <box style={{ width }}><text>Animating...</text></box>
```
## Common Patterns
### Multi-view Application
```tsx
import { useState } from "react"
import { useKeyboard } from "@opentui/react"
type View = "home" | "settings" | "help"
function App() {
const [view, setView] = useState<View>("home")
useKeyboard((key) => {
if (key.name === "1") setView("home")
if (key.name === "2") setView("settings")
if (key.name === "3") setView("help")
if (key.name === "escape") process.exit(0)
})
return (
<box style={{ flexDirection: "column", width: "100%", height: "100%" }}>
<Header view={view} />
{view === "home" && <HomeView />}
{view === "settings" && <SettingsView />}
{view === "help" && <HelpView />}
<Footer />
</box>
)
}
```
### Modal Pattern
```tsx
import { useState } from "react"
import { useKeyboard } from "@opentui/react"
function App() {
const [showModal, setShowModal] = useState(false)
useKeyboard((key) => {
if (showModal) return // Modal handles its own keys
if (key.name === "m") {
setShowModal(true)
}
})
return (
<>
<MainContent />
{showModal && (
<Modal onClose={() => setShowModal(false)} />
)}
</>
)
}
function Modal({ onClose }: { onClose: () => void }) {
const [input, setInput] = useState("")
useKeyboard((key) => {
if (key.name === "escape") onClose()
})
return (
<box
style={{
position: "absolute",
top: "50%",
left: "50%",
width: 60,
height: 10,
backgroundColor: "#1a1a1a",
border: true,
borderColor: "#FFFFFF",
}}
>
<input
placeholder="Enter value..."
value={input}
focused={true}
onInput={setInput}
onSubmit={(val) => {
console.log("Submitted:", val)
onClose()
}}
/>
</box>
)
}
```
### Focus Management
```tsx
import { useState } from "react"
import { useKeyboard } from "@opentui/react"
type FocusPanel = "left" | "right"
function App() {
const [focused, setFocused] = useState<FocusPanel>("left")
useKeyboard((key) => {
if (key.name === "tab") {
setFocused(focused === "left" ? "right" : "left")
}
})
return (
<box style={{ flexDirection: "row", width: "100%", height: "100%" }}>
<box
title="Left Panel"
style={{ flexGrow: 1, border: true }}
focused={focused === "left"}
>
<text>Content</text>
</box>
<box
title="Right Panel"
style={{ flexGrow: 1, border: true }}
focused={focused === "right"}
>
<text>Content</text>
</box>
</box>
)
}
```
### List with Selection
```tsx
import { useState } from "react"
import { useKeyboard } from "@opentui/react"
function FileList({ files }: { files: string[] }) {
const [selected, setSelected] = useState(0)
useKeyboard((key) => {
if (key.name === "up") {
setSelected(Math.max(0, selected - 1))
}
if (key.name === "down") {
setSelected(Math.min(files.length - 1, selected + 1))
}
if (key.name === "enter") {
console.log("Selected:", files[selected])
}
})
return (
<box style={{ flexDirection: "column" }}>
{files.map((file, idx) => (
<text
key={file}
fg={idx === selected ? "#00FF00" : "#FFFFFF"}
bg={idx === selected ? "#333333" : undefined}
>
{idx === selected ? "> " : " "}
{file}
</text>
))}
</box>
)
}
```
### Responsive Layout
```tsx
import { useTerminalDimensions } from "@opentui/react"
function App() {
const { width, height } = useTerminalDimensions()
// Show simplified layout on small terminals
const isSmall = width < 80
return (
<box style={{ flexDirection: isSmall ? "column" : "row", width, height }}>
<Sidebar width={isSmall ? width : 30} />
<MainContent width={isSmall ? width : width - 30} />
</box>
)
}
```
### Loading Spinner
```tsx
import { useState, useEffect } from "react"
function Spinner() {
const [frame, setFrame] = useState(0)
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
useEffect(() => {
const interval = setInterval(() => {
setFrame((f) => (f + 1) % frames.length)
}, 80)
return () => clearInterval(interval)
}, [])
return <text fg="#00FFFF">{frames[frame]} Loading...</text>
}
```
## Code Style Guidelines
### Import Order
1. External dependencies (React, etc.)
2. OpenTUI packages
3. Internal utilities
4. Components
5. Type imports (with `type` keyword)
```tsx
import { useState, useEffect } from "react"
import { useKeyboard, useRenderer } from "@opentui/react"
import { RGBA, TextAttributes } from "@opentui/core"
import { formatTime } from "./utils"
import { Header } from "./components/Header"
import type { AppState, View } from "./types"
```
### Naming Conventions
- **camelCase**: variables, functions, parameters
- **PascalCase**: components, classes, types/interfaces
- **UPPER_CASE**: constants
```tsx
// Good
const userInput = "text"
function handleSubmit() {}
interface UserData {}
const MAX_RETRIES = 3
// Bad
const UserInput = "text"
function HandleSubmit() {}
interface userData {}
```
### Component Structure
```tsx
interface ComponentProps {
title: string
items: string[]
}
export function Component({ title, items }: ComponentProps) {
// 1. Hooks
const [selected, setSelected] = useState(0)
const renderer = useRenderer()
// 2. Event handlers
useKeyboard((key) => {
if (key.name === "escape") process.exit(0)
})
// 3. Effects
useEffect(() => {
// Side effects
}, [])
// 4. Helper functions
const handleSelect = (index: number) => {
setSelected(index)
}
// 5. JSX return
return (
<box>
<text>{title}</text>
{/* Content */}
</box>
)
}
```
## Best Practices
### Performance
1. **Minimize re-renders**: Use `useMemo` and `useCallback` for expensive computations
2. **Avoid frequent updates**: Throttle or debounce rapid state changes
3. **Use keys properly**: Provide stable keys for lists to optimize reconciliation
4. **Optimize large lists**: Use virtualization or pagination for long lists
### Focus Management
1. **Only one focused component**: Ensure only one input/select has `focused={true}` at a time
2. **Keyboard routing**: Check focus state before handling keyboard events
3. **Visual feedback**: Use `focused` prop on boxes to show focus state with borders
### Layout Tips
1. **Use flexbox**: Leverage `flexDirection`, `justifyContent`, `alignItems` for layouts
2. **Percentage widths**: Use `"50%"` for responsive layouts
3. **flexGrow/flexShrink**: Use for responsive sizing
4. **Absolute positioning**: Use sparingly for modals/overlays
### Debugging
1. **Console overlay**: Use `renderer.console.show()` instead of `console.log`
2. **Toggle console**: Press backtick (`) to toggle console visibility
3. **Environment variables**: Set `OTUI_DEBUG=true` for debug mode
4. **Stats overlay**: Set `OTUI_SHOW_STATS=true` to see performance stats
### Error Handling
1. **Graceful degradation**: Handle terminal resize and small screens
2. **Input validation**: Validate user input before processing
3. **Error boundaries**: Wrap components in error boundaries for production
## Common Gotchas
1. **Console captured**: `console.log` goes to overlay, use `renderer.console.show()` to view
2. **Focus required**: Inputs/selects need `focused={true}` to receive events
3. **Bun only**: OpenTUI is designed for Bun runtime, not Node.js
4. **Zig for builds**: Only needed when modifying native code, not for TypeScript changes
5. **Alternate screen**: Default is full-screen mode, use `useAlternateScreen: false` to disable
6. **Key names**: Use lowercase names ("a", "enter", "escape"), not "A", "Enter", "Escape"
## Troubleshooting
### Application not rendering
- Ensure you called `renderer.start()` after `createRoot().render()`
- Check terminal supports ANSI escape codes
- Try disabling alternate screen: `useAlternateScreen: false`
### Keyboard input not working
- Ensure component has `focused={true}`
- Check if modal or overlay is capturing input
- Verify `useKeyboard` hook is registered
### Layout issues
- Check parent has explicit dimensions (`width`, `height`)
- Use `flexGrow` for flexible sizing
- Ensure `flexDirection` is set correctly
- Use browser DevTools-like thinking (Yoga = Flexbox)
### Colors not showing
- Verify terminal supports 24-bit color
- Use hex format: `"#RRGGBB"`
- Check color values are valid (0-255 for RGB)
## Reference Documentation
For detailed API references and advanced topics, see:
- [Component API Reference](references/COMPONENT_API.md) - Complete component props and options
- [Core API Reference](references/CORE_API.md) - Low-level Core package API
- [Layout Guide](references/LAYOUT.md) - In-depth Yoga/Flexbox layout guide
- [Styling Guide](references/STYLING.md) - Colors, text attributes, and styling patterns
## Examples
The OpenTUI repository contains many examples:
- Basic counter app
- Login form with validation
- File explorer with keyboard navigation
- Code viewer with syntax highlighting
- Git diff viewer
- Multi-view dashboard
- Modal dialogs
- Tab navigation
## Resources
- **GitHub**: https://github.com/anomalyco/opentui
- **NPM Core**: https://www.npmjs.com/package/@opentui/core
- **NPM React**: https://www.npmjs.com/package/@opentui/react
- **Awesome OpenTUI**: https://github.com/msmps/awesome-opentui
- **Create TUI**: https://github.com/msmps/create-tui
## Development Commands
```bash
# Install dependencies
bun install
# Run application
bun run index.tsx
# Type check
bun --bun tsc --noEmit
# Build project (if applicable)
bun run build
```
## Next Steps
After implementing a basic OpenTUI application:
1. **Add keyboard shortcuts**: Implement common shortcuts (Ctrl+Q, etc.)
2. **Create themes**: Extract colors to theme objects for consistency
3. **Add help screen**: Show keyboard shortcuts and commands
4. **Implement error handling**: Gracefully handle edge cases
5. **Optimize performance**: Profile and optimize render performance
6. **Add tests**: Write unit tests for components and logic
7. **Document usage**: Add README with screenshots and usage guideMore Accessibility skills
skill-creator
anthropics/skills
Create new skills, modify and improve existing skills, and measure skill performance. Use when users want to create a skill from scratch, edit, or optimize an existing skill, run evals to test a skill, benchmark skill performance with variance analysis, or optimize a skill's description for better triggering accuracy.
hyperframes-core
heygen-com/hyperframes
The HyperFrames composition contract — build one renderable project. Use for composition structure, the `data-*` timing attributes, `class="clip"`, tracks, sub-compositions, variables, framework-owned media playback, deterministic-render rules, and validation. Also covers Tailwind projects and the STORYBOARD.md / SCRIPT.md plan formats. Read before writing composition HTML.
ui-ux-pro-max
nextlevelbuilder/ui-ux-pro-max-skill
UI/UX design intelligence for web, mobile, and desktop. This skill should be used when designing, building, reviewing, or fixing interfaces, including pages, components, design systems, accessibility, interaction, responsive layout, typography, color, charts, and stack-specific UI implementation. Searchable local data: 79 searchable styles (50 active), 192 product palettes and reasoning profiles, 74 font pairings, 119 UX guidelines, 105 icons, 17 GSAP presets, 25 chart types, and 22 stacks.

