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 IDscrollChildIntoView(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 forScrollBarRenderableonly when you need a scrollbar decoupled from ascrollboxviewport.
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>