All skills
msmps avatar

/opentui

@0d61d6d
by Matt Simpsonmsmps/opentui-skill225 stars
9

OpenTUI skill for building terminal user interfaces with the Core, React, or Solid APIs. Use for any TUI task including components, layout, keyboard and keymap handling, animations, and testing.

Use this Skill: https://skilld.dev/gh/msmps/opentui-skill/opentui

This session only. Nothing lands on disk.

referencescomponentscontainers.md

≈2.8k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Container Components

Components for grouping and organizing content in OpenTUI.

Box Component

The primary container component with borders, backgrounds, and layout capabilities.

Basic Usage

// React/Solid
<box>
  <text>Content inside box</text>
</box>

// Core
const box = new BoxRenderable(renderer, {
  id: "container",
})
box.add(child)

Borders

<box border>
  Simple border
</box>

<box
  border
  borderStyle="single"    // single | double | rounded | heavy
  borderColor="#FFFFFF"
>
  Styled border
</box>

// Selected sides
<box border={["top", "bottom"]}>
  Top and bottom only
</box>

Border Styles:

Style Appearance
single ┌─┐│ │└─┘
double ╔═╗║ ║╚═╝
rounded ╭─╮│ │╰─╯
heavy ┏━┓┃ ┃┗━┛

Title

<box
  border
  title="Settings"
  titleColor="#FFCC00"          // Title text color (defaults to border color)
  titleAlignment="center"       // left | center | right
  bottomTitle="Press q to quit" // Title text in the bottom border
  bottomTitleAlignment="right"  // left | center | right
>
  Panel content
</box>
Prop Type Default Description
title string – Title text in the top border
titleColor string | RGBA border color Color of the title text
titleAlignment "left" | "center" | "right" "left" Top title position
bottomTitle string – Title text in the bottom border
bottomTitleAlignment "left" | "center" | "right" "left" Bottom title position

Background

<box backgroundColor="#1a1a2e">
  Dark background
</box>

<box backgroundColor="transparent">
  No background
</box>

Layout

Boxes are flex containers by default:

<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}                   // Space between children
>
  <text>Item 1</text>
  <text>Item 2</text>
</box>

Spacing

<box
  padding={2}               // All sides
  paddingTop={1}
  paddingRight={2}
  paddingBottom={1}
  paddingLeft={2}
  paddingX={2}              // Horizontal (left + right)
  paddingY={1}              // Vertical (top + bottom)
  margin={1}
  marginTop={1}
  marginX={2}               // Horizontal (left + right)
  marginY={1}               // Vertical (top + bottom)
>
  Spaced content
</box>

Dimensions

<box
  width={40}                // Fixed width
  height={10}               // Fixed height
  width="50%"               // Percentage of parent
  minWidth={20}             // Minimum width
  maxWidth={80}             // Maximum width
  flexGrow={1}              // Grow to fill space
>
  Sized box
</box>

Mouse Events

<box
  onMouseDown={(event) => {
    console.log("Clicked at:", event.x, event.y)
  }}
  onMouseUp={(event) => {}}
  onMouseMove={(event) => {}}
>
  Clickable box
</box>

Focusable Boxes

By default, Box elements are not focusable. Set the focusable prop to enable focus behavior:

// Make a box focusable - it can receive focus via mouse click
<box focusable border>
  <text>Click to focus</text>
</box>

// Controlled focus state
const [focused, setFocused] = useState(false)

<box
  focusable
  focused={focused}
  border
  borderColor={focused ? "#00ff00" : "#888"}
>
  <text>{focused ? "Focused!" : "Not focused"}</text>
</box>

When a focusable Box is clicked, focus bubbles up from the click target to the nearest focusable parent. Use event.preventDefault() in onMouseDown to prevent auto-focus.

ScrollBox Component

A scrollable container for content that exceeds the viewport.

Basic Usage

// React
<scrollbox height={10}>
  {items.map((item, i) => (
    <text key={i}>{item}</text>
  ))}
</scrollbox>

// Solid
<scrollbox height={10}>
  <For each={items()}>
    {(item) => <text>{item}</text>}
  </For>
</scrollbox>

// Core
const scrollbox = new ScrollBoxRenderable(renderer, {
  id: "list",
  height: 10,
})
items.forEach(item => {
  scrollbox.add(new TextRenderable(renderer, { content: item }))
})

Focus for Keyboard Scrolling

<scrollbox focused height={20}>
  {/* Use arrow keys to scroll */}
</scrollbox>

Scrollbar Styling

// React
<scrollbox
  style={{
    rootOptions: {
      backgroundColor: "#24283b",
    },
    wrapperOptions: {
      backgroundColor: "#1f2335",
    },
    viewportOptions: {
      backgroundColor: "#1a1b26",
    },
    contentOptions: {
      backgroundColor: "#16161e",
    },
    scrollbarOptions: {
      showArrows: true,
      trackOptions: {
        foregroundColor: "#7aa2f7",
        backgroundColor: "#414868",
      },
    },
  }}
>
  {content}
</scrollbox>

Scroll Position (Core)

const scrollbox = new ScrollBoxRenderable(renderer, {
  id: "list",
  height: 20,
})

// Scroll programmatically
scrollbox.scrollTo(0)           // Scroll to top
scrollbox.scrollTo(100)         // Scroll to position
scrollbox.scrollBy(10)          // Scroll relative
scrollbox.scrollToBottom()      // Scroll to end

// Scroll a child into view (nearest alignment)
scrollbox.scrollChildIntoView("child-id")  // Searches descendants by ID

scrollChildIntoView(childId) scrolls the minimum amount needed to make the identified descendant visible. It mirrors Element.scrollIntoView({ block: "nearest" }) from the CSSOM View spec. Works with nested descendants and handles both horizontal and vertical scrolling.

ScrollBar Component

A standalone scrollbar (separate from scrollbox) with optional arrows, a draggable thumb, and keyboard navigation. ScrollBarRenderable is exported from @opentui/core. Connect it to any content by wiring its size/position props.

import { ScrollBarRenderable } from "@opentui/core"

const scrollbar = new ScrollBarRenderable(renderer, {
  id: "scrollbar",
  orientation: "vertical",   // "vertical" | "horizontal"
  showArrows: false,
  scrollSize: 0,             // Total content size
  viewportSize: 0,           // Visible size
  scrollPosition: 0,         // Current position
  onChange: (position) => {
    // Sync your content to the new scroll position
  },
})

// Drive it from your content dimensions, then focus for keyboard control
scrollbar.scrollSize = content.length
scrollbar.viewportSize = visibleRows
scrollbar.scrollPosition = 0
scrollbar.focus()
Prop Type Default
orientation "vertical" | "horizontal" –
showArrows boolean false
arrowOptions ArrowOptions –
trackOptions Partial<SliderOptions> –
scrollSize number 0
viewportSize number 0
scrollPosition number 0
scrollStep number –
onChange (position: number) => void –

When focused: arrows / hjkl, PageUp/PageDown, Home/End.

Most apps should use scrollbox (which embeds a scrollbar). Reach for ScrollBarRenderable only when you need a scrollbar decoupled from a scrollbox viewport.

Embedded Terminal Component

EmbeddedTerminalRenderable is a Core-only Ghostty VT parser and screen. It is not a process or PTY: write child output into it and send onData bytes back to the child.

import { EmbeddedTerminalRenderable } from "@opentui/core"

const terminal = new EmbeddedTerminalRenderable(renderer, {
  width: 80,
  height: 24,
  maxScrollback: 10_000, // Bytes, not lines
  onData(data, source) {
    child.write(data)    // source is "input" or "response"
  },
  onTerminalResize(cols, rows) {
    child.resize(cols, rows)
  },
})

renderer.root.add(terminal)
terminal.write(childOutput) // string | Uint8Array
terminal.focus()

Key methods are write(), encodeKey(), encodePaste(), screen(), invalidate(), focus(), and blur(). The renderable handles focused key, paste, mouse, cursor, scrollback, and selection behavior. selectable defaults to true; cols/rows default to numeric layout dimensions or 80x24. Destroy the child and renderable together.

React and Solid do not register an element for this component. Use Core or register an adapter with extend(); catalogue registration alone cannot supply non-default constructor-only cols, rows, or maxScrollback in Solid.

Composition Patterns

Card Component

function Card({ title, children }) {
  return (
    <box
      border
      borderStyle="rounded"
      padding={2}
      marginBottom={1}
    >
      {title && (
        <text fg="#00FFFF"><strong>{title}</strong></text>
      )}
      <box marginTop={title ? 1 : 0}>
        {children}
      </box>
    </box>
  )
}

Panel Component

function Panel({ title, children, width = 40 }) {
  return (
    <box
      border
      borderStyle="double"
      width={width}
      backgroundColor="#1a1a2e"
    >
      {title && (
        <box
          border={["bottom"]}
          padding={1}
          backgroundColor="#2a2a4e"
        >
          <text><strong>{title}</strong></text>
        </box>
      )}
      <box padding={2}>
        {children}
      </box>
    </box>
  )
}

List Container

function List({ items, renderItem }) {
  return (
    <scrollbox height={15} focused>
      {items.map((item, i) => (
        <box
          key={i}
          padding={1}
          backgroundColor={i % 2 === 0 ? "#222" : "#333"}
        >
          {renderItem(item, i)}
        </box>
      ))}
    </scrollbox>
  )
}

Nesting Containers

<box flexDirection="column" height="100%">
  {/* Header */}
  <box height={3} border>
    <text>Header</text>
  </box>
  
  {/* Main area with sidebar */}
  <box flexDirection="row" flexGrow={1}>
    <box width={20} border>
      <text>Sidebar</text>
    </box>
    <box flexGrow={1}>
      <scrollbox height="100%">
        {/* Scrollable content */}
      </scrollbox>
    </box>
  </box>
  
  {/* Footer */}
  <box height={1}>
    <text>Footer</text>
  </box>
</box>

Gotchas

Percentage Dimensions Need Parent Size

// WRONG - parent has no explicit size
<box>
  <box width="50%">Won't work</box>
</box>

// CORRECT
<box width="100%">
  <box width="50%">Works</box>
</box>

FlexGrow Needs Sized Parent

// WRONG
<box>
  <box flexGrow={1}>Won't grow</box>
</box>

// CORRECT
<box height="100%">
  <box flexGrow={1}>Will grow</box>
</box>

ScrollBox Needs Height

// WRONG - no height constraint
<scrollbox>
  {items}
</scrollbox>

// CORRECT
<scrollbox height={20}>
  {items}
</scrollbox>

Borders Add to Size

Borders take up space inside the box:

<box width={10} border>
  {/* Inner content area is 8 chars (10 - 2 for borders) */}
</box>

Source: SKILL.md on GitHub

No alerts17d5 checks · Risk SAFE
  • Gen Agent Trust Hub17d

    This skill contains documentation and usage patterns for the OpenTUI framework, designed for building terminal user interfaces across Core, React, and SolidJS. The analysis confirmed that the skill provides legitimate development guidance; no malicious code, persistence mechanisms, or obfuscation were detected. Findings are limited to standard platform capabilities and recommended scaffolding tools.

  • Socket17d

    No alerts

  • Snyk17d

    Risk: LOW · No issues

  • Runlayer7mo

    1/26 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 0d61d6d. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub last month.

Steadyupdated last month
Other metadata
metadata
{
  "references": "core, react, solid, components, layout, keyboard, keymap, animation, testing"
}
  • React
  • Testing
  • terminal-ui
  • tui
  • solid
  • components
  • keyboard-input
  • animations
  • layout

README badge

README badge for msmps/opentui-skill

Builds terminal user interfaces with OpenTUI using an imperative core API or React/Solid reconcilers. Covers layout, keyboard handling, animations, components, and testing for full-featured TUI applications.

Generated from the current SKILL.md.

Does this skill cover all three OpenTUI frameworks?
Yes. The skill consolidates the core imperative API, React reconciler, and Solid reconciler. Use the decision tree in the skill to pick the right framework for your task.
How do I start a new OpenTUI project?
Use `create-tui` with options before arguments: `bunx create-tui -t react my-app` (not `bunx create-tui my-app -t react`). Then read the REFERENCE.md for your chosen framework.
What runtime does OpenTUI require?
OpenTUI runs on Bun and uses Zig for native builds. See `./references/core/gotchas.md` for runtime requirements and build guidance.
Can I use `process.exit()` to shut down my TUI?
No. Always use `renderer.destroy()` instead. Calling `process.exit()` directly is a common pitfall covered in `core/gotchas.md`.
How do I apply text styling in React or Solid?
Use nested modifier elements, not props. See `components/text-display.md` for the correct pattern.

Generated from the current SKILL.md. These answers refresh after source changes.