Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
dimitrigilbert avatar

Opentui React

  • 31 installs
  • 5 repo stars
  • Updated June 3, 2026
  • dimitrigilbert/ai-skills

Build terminal UIs with OpenTUI and React using hooks like useKeyboard, useRenderer, and JSX patterns.

About

Expert guidance for building terminal UIs with OpenTUI and React, including hooks and JSX patterns. A developer uses it to write React-based TUI components with state and forms.

  • Builds terminal UIs with OpenTUI and React using createRoot and JSX elements
  • React hooks useKeyboard, useRenderer, and useTimeline for input and rendering

Opentui React by the numbers

  • 31 all-time installs (skills.sh)
  • Ranked #1,450 of 2,245 Frontend Development skills by installs in the Skillselion catalog
  • Data as of Jul 29, 2026 (Skillselion catalog sync)
npx skills add https://github.com/dimitrigilbert/ai-skills --skill opentui-react

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs31
repo stars5
Last updatedJune 3, 2026
Repositorydimitrigilbert/ai-skills

What it does

Build terminal UIs with OpenTUI and React using hooks like useKeyboard, useRenderer, and JSX patterns.

Files

SKILL.mdMarkdownGitHub ↗

OpenTUI React Integration

Expert assistance for building terminal UIs with OpenTUI and React.

Quick Start

# Install dependencies
bun install @opentui/core @opentui/react react

Basic Setup

import { createCliRenderer } from "@opentui/core"
import { createRoot } from "@opentui/react"

function App() {
  return <text>Hello, OpenTUI React!</text>
}

async function main() {
  const renderer = await createCliRenderer()
  createRoot(renderer).render(<App />)
}

main()

React Hooks

useKeyboard

Handle keyboard events in React components.

import { useKeyboard } from "@opentui/react"

function App() {
  useKeyboard((key) => {
    if (key.name === "c" && key.ctrl) {
      process.exit(0)
    }
    if (key.name === "q") {
      process.exit(0)
    }
  })

  return <text>Press Ctrl+C or q to exit</text>
}

useRenderer

Access the renderer instance.

import { useRenderer } from "@opentui/react"

function Component() {
  const renderer = useRenderer()

  const handleClick = () => {
    console.log("Renderer available:", !!renderer)
  }

  return <box onClick={handleClick}>Click me</box>
}

useTerminalDimensions

Get terminal size changes.

import { useTerminalDimensions } from "@opentui/react"

function Responsive() {
  const { width, height } = useTerminalDimensions()

  return (
    <box>
      <text>Terminal: {width}x{height}</text>
    </box>
  )
}

useTimeline

Create animations in React.

import { useTimeline } from "@opentui/react"
import { useRef } from "react"

function AnimatedBox() {
  const boxRef = useRef<any>(null)

  const timeline = useTimeline({
    duration: 1000,
    easing: (t) => t * (2 - t), // easeOutQuad
  })

  const animate = () => {
    if (boxRef.current) {
      timeline.to(boxRef.current, {
        backgroundColor: { r: 255, g: 0, b: 0 },
      })
      timeline.play()
    }
  }

  return (
    <box ref={boxRef} onClick={animate}>
      <text>Click to animate</text>
    </box>
  )
}

React Components

All OpenTUI components are available as JSX elements:

import {
  text,
  box,
  input,
  select,
  scrollbox,
  code,
} from "@opentui/react"

function Form() {
  return (
    <box flexDirection="column" gap={1}>
      <text decoration="bold">User Information</text>

      <input placeholder="Name" />
      <input placeholder="Email" />

      <select
        options={[
          { label: "Option 1", value: "1" },
          { label: "Option 2", value: "2" },
        ]}
      />

      <box borderStyle="single">
        <text>Submit</text>
      </box>
    </box>
  )
}

Styling in React

Styles are passed as props to components:

function StyledComponent() {
  return (
    <box
      borderStyle="double"
      borderColor={{ r: 100, g: 149, b: 237 }}
      backgroundColor={{ r: 30, g: 30, b: 30 }}
      padding={1}
    >
      <text
        foregroundColor={{ r: 255, g: 255, b: 255 }}
        decoration="bold underline"
      >
        Styled Text
      </text>
    </box>
  )
}

Color format: { r: number, g: number, b: number, a?: number }

State Management

Local State

import { useState } from "react"

function Counter() {
  const [count, setCount] = useState(0)

  useKeyboard((key) => {
    if (key.name === "up") setCount(c => c + 1)
    if (key.name === "down") setCount(c => c - 1)
  })

  return (
    <box>
      <text>Count: {count}</text>
      <text>Use arrow keys</text>
    </box>
  )
}

Form State

function LoginForm() {
  const [email, setEmail] = useState("")
  const [password, setPassword] = useState("")
  const [errors, setErrors] = useState<any>({})

  const handleSubmit = () => {
    const newErrors: any = {}

    if (!email.includes("@")) {
      newErrors.email = "Invalid email"
    }
    if (password.length < 8) {
      newErrors.password = "Password too short"
    }

    if (Object.keys(newErrors).length > 0) {
      setErrors(newErrors)
      return
    }

    console.log("Login:", { email, password })
  }

  return (
    <box flexDirection="column" gap={1}>
      <text decoration="bold">Login</text>

      <input
        value={email}
        onChange={setEmail}
        placeholder="Email"
      />
      {errors.email && (
        <text foregroundColor={{ r: 231, g: 76, b: 60 }}>
          {errors.email}
        </text>
      )}

      <input
        value={password}
        onChange={setPassword}
        placeholder="Password"
        password
      />
      {errors.password && (
        <text foregroundColor={{ r: 231, g: 76, b: 60 }}>
          {errors.password}
        </text>
      )}

      <box onClick={handleSubmit} borderStyle="single">
        <text>Submit</text>
      </box>
    </box>
  )
}

External State Management

Redux Integration
import { Provider, useSelector, useDispatch } from "react-redux"

function Counter() {
  const count = useSelector((state: any) => state.count)
  const dispatch = useDispatch()

  useKeyboard((key) => {
    if (key.name === "up") dispatch({ type: "INCREMENT" })
    if (key.name === "down") dispatch({ type: "DECREMENT" })
  })

  return <text>Count: {count}</text>
}
Zustand Integration
import { create } from "zustand"

const useStore = create((set) => ({
  count: 0,
  increment: () => set((state: any) => ({ count: state.count + 1 })),
  decrement: () => set((state: any) => ({ count: state.count - 1 })),
}))

function Counter() {
  const { count, increment, decrement } = useStore()

  useKeyboard((key) => {
    if (key.name === "up") increment()
    if (key.name === "down") decrement()
  })

  return <text>Count: {count}</text>
}

Common Patterns

List with Selection

function SelectList({ items }: { items: string[] }) {
  const [selectedIndex, setSelectedIndex] = useState(0)

  useKeyboard((key) => {
    if (key.name === "down" || (key.name === "tab" && !key.shift)) {
      setSelectedIndex(i => Math.min(i + 1, items.length - 1))
    }
    if (key.name === "up" || (key.name === "tab" && key.shift)) {
      setSelectedIndex(i => Math.max(i - 1, 0))
    }
    if (key.name === "enter") {
      console.log("Selected:", items[selectedIndex])
    }
  })

  return (
    <scrollbox height={20}>
      {items.map((item, index) => (
        <box
          key={index}
          backgroundColor={
            index === selectedIndex
              ? { r: 100, g: 149, b: 237 }
              : { r: 30, g: 30, b: 30 }
          }
        >
          <text
            foregroundColor={
              index === selectedIndex
                ? { r: 255, g: 255, b: 255 }
                : { r: 255, g: 255, b: 255 }
            }
          >
            {index === selectedIndex ? "> " : "  "}{item}
          </text>
        </box>
      ))}
    </scrollbox>
  )
}

Tabs

function Tabs({ tabs }: { tabs: Array<{ id: string, label: string, content: any }> }) {
  const [activeTab, setActiveTab] = useState(tabs[0].id)

  return (
    <box flexDirection="column" height={30}>
      {/* Tab headers */}
      <box flexDirection="row">
        {tabs.map(tab => (
          <box
            key={tab.id}
            onClick={() => setActiveTab(tab.id)}
            borderStyle={activeTab === tab.id ? "single" : "none"}
            backgroundColor={
              activeTab === tab.id
                ? { r: 100, g: 149, b: 237 }
                : { r: 50, g: 50, b: 50 }
            }
            padding={1}
          >
            <text>{tab.label}</text>
          </box>
        ))}
      </box>

      {/* Tab content */}
      <box flexGrow={1} padding={1}>
        {tabs.find(t => t.id === activeTab)?.content}
      </box>
    </box>
  )
}

Modal/Dialog

function Modal({ isOpen, onClose, children }: any) {
  if (!isOpen) return null

  return (
    <box
      position="absolute"
      top={0}
      left={0}
      width="100%"
      height="100%"
      backgroundColor={{ r: 0, g: 0, b: 0, a: 0.5 }}
      justifyContent="center"
      alignItems="center"
      onClick={onClose}
    >
      <box
        borderStyle="double"
        backgroundColor={{ r: 30, g: 30, b: 30 }}
        padding={2}
        onClick={(e: any) => e.stopPropagation()}
      >
        {children}
      </box>
    </box>
  )
}

When to Use This Skill

Use /opentui-react for:

  • Building TUIs with React
  • Using hooks (useKeyboard, useRenderer, etc.)
  • JSX-style component development
  • Integrating with React state management
  • Testing React OpenTUI components

For vanilla TypeScript/JavaScript, use /opentui For SolidJS development, use /opentui-solid For project scaffolding, use /opentui-projects

Resources

  • Hooks Reference - Complete hook documentation
  • Component Props - React component props
  • Patterns - Common React patterns
  • State Management - React state integration
  • Testing - Testing React components

Key Knowledge Sources

  • OpenTUI React GitHub: https://github.com/sst/opentui/tree/main/packages/react
  • Context7: /sst/opentui - React integration queries
  • Research: .search-data/research/opentui/

Related skills

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.