Skip to content

About

resizable dynamic layouts for opentui

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Repository files navigation

opentui-split

Resizable split panes for OpenTUI, with Core, React, and Solid exports.

Install

Requires OpenTUI 0.6.6 or later within 0.6.x, with Bun 1.3.14+ or Node.js 26.4.0+. When using Node.js, launch your app with node --experimental-ffi. For OpenTUI 0.5.x, use opentui-split 0.3.x.

npm install opentui-split
pnpm add opentui-split
bun add opentui-split

Usage

Every pane except the last uses a fixed size. The last pane fills the remaining space. Drag a gutter to resize its adjacent panes.

Sizes use whole terminal cells within the split's content area, excluding its border, padding, and gutters. Oversized initial allocations shrink to fit. Resizing preserves pane proportions, including after its collapse to zero size.
minSizes constrains dragging, not container shrinking.

Padding accepts individual edges and padding, paddingX, and paddingY shorthands. Percentage padding resolves against the containing block's content width on both axes, including when that width changes.

Core

import { SplitPaneRenderable } from "opentui-split";

const outer = new SplitPaneRenderable(renderer, {
  id: "outer",
  direction: "horizontal",
  onSizesChange: (sizes) => saveLayout(sizes),
});
outer.addPane(sidebar, 26, 16);
outer.addPane(mainArea, 80, 20);

const right = new SplitPaneRenderable(renderer, {
  id: "right",
  direction: "vertical",
});
outer.addPane(right, 80, 20);
right.addPane(top, 14, 5);
right.addPane(bottom, 12, 4);

addPane accepts the pane, initial size, and minimum size. A pane cannot contain itself or be inserted into one of its descendants.

React

/** @jsxImportSource @opentui/react */
import "opentui-split/react";

export function Layout() {
  return (
    <split-pane id="outer" direction="horizontal" sizes={[26, 80]} minSizes={[16, 20]}>
      <box id="sidebar" border title="Navigation" />
      <box id="main" border title="Content" />
    </split-pane>
  );
}

Solid

/** @jsxImportSource @opentui/solid */
import "opentui-split/solid";

export function Layout() {
  return (
    <split_pane id="outer" direction="horizontal" sizes={[26, 80]} minSizes={[16, 20]}>
      <box id="sidebar" border title="Navigation" />
      <box id="main" border title="Content" />
    </split_pane>
  );
}

The adapter imports register their components. If a bundler removes that side effect, call registerSplitPane() once at startup.

Split options

SplitPaneOptions accepts OpenTUI BoxOptions and these options:

Option Type Default Description
direction "horizontal" | "vertical" "horizontal" Pane layout direction.
sizes number[] 20 / pane Initial sizes in cells. The last pane fills the remaining space.
minSizes number[] 4 / pane Minimum sizes while dragging. Fractional values round up.
gutterSize number 1 Gutter width or height in cells.
gutterOptions SplitPaneGutterOptions See below Appearance shared by generated gutters.
onSizesChange (sizes: number[]) => void None Called after a drag or container resize.

gutterOptions accepts visible, color, and direction-specific glyphs. A hidden gutter remains draggable too. It accepts the same fields when constructing a standalone gutter.
Runtime appearance and size updates reuse existing gutters.

Keyboard focus

createPaneNavigator moves focus through visible focusable renderables with Tab and Shift+Tab. Set focusedBorderColor on panes to show focus state.

import { createPaneNavigator } from "opentui-split";

const navigator = createPaneNavigator(renderer);
Option Type Default Description
root BaseRenderable Renderer root Subtree to search.
keymap (event) => "next" | "previous" | undefined | false Tab keys Maps keys or disables automatic handling.
wrap boolean true Wrap focus at either end.
isPane (renderable) => boolean Any focusable Selects focusable panes.
onFocusChange (current, previous) => void None Runs after focus changes.

Set keymap to false to use focusNext(), focusPrevious(), and focusPane() manually. Call dispose() to unbind the keys.

Pane movement

createPaneMovement handles mouse and keyboard movement. Dragging reorders panes within one split or swaps panes across splits. Option/Alt + arrow is the default keyboard shortcut.

import { createPaneMovement } from "opentui-split";

const movement = createPaneMovement(renderer, {
  modifier: false,
  handle: (node) => node.id.endsWith("-title"),
  onReorder: (split, from, to) => saveLayout(split.sizes),
  onSwap: () => saveLayout(),
});
Option Type Default Description
root Renderable Renderer root Subtree whose panes can move.
modifier "ctrl" | "alt" | "shift" | false "ctrl" Required drag key. false allows any drag.
handle (target, pane) => boolean Whole pane Restricts where a drag can begin.
cursor "default" | "pointer" | "move" | "crosshair" | ... | false "move" Mouse pointer shown while dragging. false keeps it unchanged.
keymap (event) => "left" | "right" | "up" | "down" | undefined | false Alt + arrow Maps keys or disables keyboard handling.
isPane (pane) => boolean Any pane Selects movable panes.
onReorder (split, from, to) => void None Runs after an in-split move.
onSwap (first, second) => void None Runs after panes exchange places across splits.

Some terminals do not report modifier keys during mouse drags. Set modifier to false and use a handle there. Create one movement controller per root; it preserves root.onMouse and specific handlers such as onMouseDown, including handlers assigned after controller creation. Hidden panes are excluded from movement. A drag is cancelled if its pane is hidden or detached before release.

Call move(direction) for manual keyboard movement and dispose() to unbind mouse and keyboard handling. Core code can use split.movePane(pane, toIndex) and read the order from split.paneList.

The default keymap also accepts macOS Alt + b/f word-motion escapes. Use createPaneDragDrop or createPaneReorder when you only need one input method. They accept the matching options from createPaneMovement.

Examples

bun examples/add-panes.ts

It supports gutter resizing, Tab focus, pane creation/deletion, drag reordering and Option/Alt + arrow keys for reordering.

License

MIT

About

resizable dynamic layouts for opentui

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages