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.

referenceslayoutpatterns.md

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

Layout Patterns

Common layout recipes for terminal user interfaces.

Full-Screen App

Fill the entire terminal:

function App() {
  return (
    <box width="100%" height="100%">
      {/* Content fills terminal */}
    </box>
  )
}

Header/Content/Footer

Classic app layout:

function AppLayout() {
  return (
    <box flexDirection="column" width="100%" height="100%">
      {/* Header - fixed height */}
      <box height={3} borderStyle="single" border={["bottom"]}>
        <text>Header</text>
      </box>
      
      {/* Content - fills remaining space */}
      <box flexGrow={1}>
        <text>Main Content</text>
      </box>
      
      {/* Footer - fixed height */}
      <box height={1}>
        <text>Status: Ready</text>
      </box>
    </box>
  )
}

Sidebar Layout

function SidebarLayout() {
  return (
    <box flexDirection="row" width="100%" height="100%">
      {/* Sidebar - fixed width */}
      <box width={25} borderStyle="single" border={["right"]}>
        <text>Sidebar</text>
      </box>
      
      {/* Main - fills remaining space */}
      <box flexGrow={1}>
        <text>Main Content</text>
      </box>
    </box>
  )
}

Resizable Sidebar

Responsive based on terminal width:

function ResponsiveSidebar() {
  const dims = useTerminalDimensions()  // React: useTerminalDimensions()
  const showSidebar = dims.width > 60
  const sidebarWidth = Math.min(30, Math.floor(dims.width * 0.3))
  
  return (
    <box flexDirection="row" width="100%" height="100%">
      {showSidebar && (
        <box width={sidebarWidth} border>
          <text>Sidebar</text>
        </box>
      )}
      <box flexGrow={1}>
        <text>Main</text>
      </box>
    </box>
  )
}

Centered Content

Horizontally Centered

<box width="100%" justifyContent="center">
  <box width={40}>
    <text>Centered horizontally</text>
  </box>
</box>

Vertically Centered

<box height="100%" alignItems="center">
  <text>Centered vertically</text>
</box>

Both Axes

<box
  width="100%"
  height="100%"
  justifyContent="center"
  alignItems="center"
>
  <box width={40} height={10} border>
    <text>Centered both ways</text>
  </box>
</box>

Modal/Dialog

Centered overlay:

function Modal({ children, visible }) {
  if (!visible) return null
  
  return (
    <box
      position="absolute"
      left={0}
      top={0}
      width="100%"
      height="100%"
      justifyContent="center"
      alignItems="center"
      backgroundColor="#00000080"
    >
      <box
        width={50}
        height={15}
        border
        borderStyle="double"
        backgroundColor="#1a1a2e"
        padding={2}
      >
        {children}
      </box>
    </box>
  )
}

Grid Layout

Using flexWrap:

function Grid({ items, columns = 3 }) {
  const itemWidth = `${Math.floor(100 / columns)}%`
  
  return (
    <box flexDirection="row" flexWrap="wrap" width="100%">
      {items.map((item, i) => (
        <box key={i} width={itemWidth} padding={1}>
          <text>{item}</text>
        </box>
      ))}
    </box>
  )
}

Split Panels

Horizontal Split

function HorizontalSplit({ ratio = 0.5 }) {
  return (
    <box flexDirection="row" width="100%" height="100%">
      <box width={`${ratio * 100}%`} border>
        <text>Left Panel</text>
      </box>
      <box flexGrow={1} border>
        <text>Right Panel</text>
      </box>
    </box>
  )
}

Vertical Split

function VerticalSplit({ ratio = 0.5 }) {
  return (
    <box flexDirection="column" width="100%" height="100%">
      <box height={`${ratio * 100}%`} border>
        <text>Top Panel</text>
      </box>
      <box flexGrow={1} border>
        <text>Bottom Panel</text>
      </box>
    </box>
  )
}

Form Layout

Label + Input pairs:

function FormField({ label, children }) {
  return (
    <box flexDirection="row" marginBottom={1}>
      <box width={15}>
        <text>{label}:</text>
      </box>
      <box flexGrow={1}>
        {children}
      </box>
    </box>
  )
}

function LoginForm() {
  return (
    <box flexDirection="column" padding={2} border width={50}>
      <FormField label="Username">
        <input placeholder="Enter username" />
      </FormField>
      <FormField label="Password">
        <input placeholder="Enter password" />
      </FormField>
      <box marginTop={2} justifyContent="flex-end">
        <box border padding={1}>
          <text>Login</text>
        </box>
      </box>
    </box>
  )
}

Navigation Tabs

function TabBar({ tabs, activeIndex, onSelect }) {
  return (
    <box flexDirection="row" border={["bottom"]}>
      {tabs.map((tab, i) => (
        <box
          key={i}
          padding={1}
          backgroundColor={i === activeIndex ? "#333" : "transparent"}
          onMouseDown={() => onSelect(i)}
        >
          <text fg={i === activeIndex ? "#fff" : "#888"}>
            {tab}
          </text>
        </box>
      ))}
    </box>
  )
}

Sticky Footer

Footer always at bottom:

function StickyFooterLayout() {
  return (
    <box flexDirection="column" width="100%" height="100%">
      {/* Content area */}
      <box flexGrow={1} flexDirection="column">
        {/* Your content here */}
        <text>Content that might be short</text>
      </box>
      
      {/* Footer pushed to bottom */}
      <box height={1}>
        <text fg="#888">Press ? for help | q to quit</text>
      </box>
    </box>
  )
}

Absolute Positioning Overlay

Tooltip or popup:

function Tooltip({ x, y, children }) {
  return (
    <box
      position="absolute"
      left={x}
      top={y}
      border
      backgroundColor="#333"
      padding={1}
      zIndex={100}
    >
      {children}
    </box>
  )
}

Responsive Breakpoints

Different layouts based on terminal size:

function ResponsiveApp() {
  const { width, height } = useTerminalDimensions()
  
  // Define breakpoints
  const isSmall = width < 60
  const isMedium = width >= 60 && width < 100
  const isLarge = width >= 100
  
  if (isSmall) {
    // Mobile-like: stacked layout
    return (
      <box flexDirection="column">
        <Navigation />
        <Content />
      </box>
    )
  }
  
  if (isMedium) {
    // Tablet-like: sidebar + content
    return (
      <box flexDirection="row">
        <box width={20}><Navigation /></box>
        <box flexGrow={1}><Content /></box>
      </box>
    )
  }
  
  // Large: full layout
  return (
    <box flexDirection="row">
      <box width={25}><Navigation /></box>
      <box flexGrow={1}><Content /></box>
      <box width={30}><Sidebar /></box>
    </box>
  )
}

Equal Height Columns

function EqualColumns() {
  return (
    <box flexDirection="row" alignItems="stretch" height={20}>
      <box flexGrow={1} border>
        <text>Short content</text>
      </box>
      <box flexGrow={1} border>
        <text>
          Longer content that
          spans multiple lines
          and takes up space
        </text>
      </box>
      <box flexGrow={1} border>
        <text>Medium content</text>
      </box>
    </box>
  )
}

Spacing Utilities

Consistent spacing patterns:

// Spacer component
function Spacer({ size = 1 }) {
  return <box height={size} width={size} />
}

// Divider component
function Divider() {
  return <box height={1} width="100%" backgroundColor="#333" />
}

// Usage
<box flexDirection="column">
  <text>Section 1</text>
  <Spacer size={2} />
  <Divider />
  <Spacer size={2} />
  <text>Section 2</text>
</box>

Axis Shorthand Props

Use paddingX/paddingY and marginX/marginY for horizontal/vertical spacing:

// Horizontal padding (left + right)
<box paddingX={4}>
  <text>4 chars padding left and right</text>
</box>

// Vertical padding (top + bottom)
<box paddingY={2}>
  <text>2 lines padding top and bottom</text>
</box>

// Horizontal margin for centering-like effect
<box marginX={10}>
  <text>Indented content</text>
</box>

// Combined for card-like spacing
<box paddingX={3} paddingY={1} marginY={1} border>
  <text>Nicely spaced card</text>
</box>

These are shorthand for:

  • paddingX={n} = paddingLeft={n} + paddingRight={n}
  • paddingY={n} = paddingTop={n} + paddingBottom={n}
  • marginX={n} = marginLeft={n} + marginRight={n}
  • marginY={n} = marginTop={n} + marginBottom={n}

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.