Skip to content

Ops

The ops module provides builders for describing terminal UI as a flat array of directives. Each frame is a complete snapshot. Build ops, pass them to term.render(), and write the output bytes.

import {
function close(): CloseElement
close
,
function open(id: string, props?: Omit<OpenElement, "directive" | "id">): OpenElement
open
,
function rgba(r: number, g: number, b: number, a?: number): number
rgba
,
function text(content: string, props?: Omit<Text, "directive" | "content">): Text
text
,
const grow: (min?: number, max?: number) => SizingAxis
grow
} from "@bomb.sh/tty";
let
let ops: (CloseElement | OpenElement | Text)[]
ops
= [
function open(id: string, props?: Omit<OpenElement, "directive" | "id">): OpenElement
open
("root", {
layout?: {
width?: SizingAxis;
height?: SizingAxis;
padding?: {
left?: number;
right?: number;
top?: number;
bottom?: number;
};
gap?: number;
direction?: "ltr" | "ttb";
alignX?: "left" | "center" | "right";
alignY?: "top" | "center" | "bottom";
}
layout
: {
width?: SizingAxis
width
:
function grow(min?: number, max?: number): SizingAxis
grow
(),
height?: SizingAxis
height
:
function grow(min?: number, max?: number): SizingAxis
grow
(),
direction?: "ttb" | "ltr"
direction
: "ttb" },
}),
function open(id: string, props?: Omit<OpenElement, "directive" | "id">): OpenElement
open
("panel", {
layout?: {
width?: SizingAxis;
height?: SizingAxis;
padding?: {
left?: number;
right?: number;
top?: number;
bottom?: number;
};
gap?: number;
direction?: "ltr" | "ttb";
alignX?: "left" | "center" | "right";
alignY?: "top" | "center" | "bottom";
}
layout
: {
padding?: {
left?: number;
right?: number;
top?: number;
bottom?: number;
}
padding
: {
left?: number
left
: 1,
right?: number
right
: 1,
top?: number
top
: 1,
bottom?: number
bottom
: 1 } },
border?: {
color: number;
bg?: number;
left?: BorderSide;
right?: BorderSide;
top?: BorderSide;
bottom?: BorderSide;
}
border
: {
color: number
color
:
function rgba(r: number, g: number, b: number, a?: number): number
rgba
(0, 255, 0),
left?: BorderSide
left
: 1,
right?: BorderSide
right
: 1,
top?: BorderSide
top
: 1,
bottom?: BorderSide
bottom
: 1 },
cornerRadius?: {
tl?: number;
tr?: number;
bl?: number;
br?: number;
}
cornerRadius
: {
tl?: number
tl
: 1,
tr?: number
tr
: 1,
bl?: number
bl
: 1,
br?: number
br
: 1 },
}),
function text(content: string, props?: Omit<Text, "directive" | "content">): Text
text
("Hello, World!"),
function close(): CloseElement
close
(),
function close(): CloseElement
close
(),
];

Opens a layout element. Every element needs a unique string id for pointer hit-testing and layout queries.

PropertyTypeDescription
widthSizingAxisHorizontal sizing (fit, grow, percent, fixed)
heightSizingAxisVertical sizing
padding{ left?, right?, top?, bottom? }Inner padding in cells
gapnumberGap between children
direction"ltr" | "ttb"Child layout direction
alignX"left" | "center" | "right"Horizontal alignment
alignY"top" | "center" | "bottom"Vertical alignment
PropertyTypeDescription
bgnumberBackground color (packed RGBA via rgba())
cornerRadius{ tl?, tr?, bl?, br? }Rounded corners
border{ color, bg?, left?, right?, top?, bottom? }Border sides (number or { width, color?, bg? })
clip{ horizontal?, vertical? }Clip overflowing content
PropertyTypeDescription
floating.x, floating.ynumberPosition offset
floating.expand{ width?, height? }Expand floating bounds
floating.attachTo"none" | "parent" | "element" | "root"Attachment target
floating.attachPoints{ element?, parent? }Anchor points
floating.pointerCaptureMode"capture" | "passthrough"Pointer event capture
floating.clipTo"none" | "attached-parent"Clip to attached parent
floating.zIndexnumberStacking order
import {
function open(id: string, props?: Omit<OpenElement, "directive" | "id">): OpenElement
open
,
function rgba(r: number, g: number, b: number, a?: number): number
rgba
} from "@bomb.sh/tty";
function open(id: string, props?: Omit<OpenElement, "directive" | "id">): OpenElement
open
("sidebar", {
layout?: {
width?: SizingAxis;
height?: SizingAxis;
padding?: {
left?: number;
right?: number;
top?: number;
bottom?: number;
};
gap?: number;
direction?: "ltr" | "ttb";
alignX?: "left" | "center" | "right";
alignY?: "top" | "center" | "bottom";
}
layout
: {
width?: SizingAxis
width
: {
type: "fixed"
type
: "fixed",
value: number
value
: 20 } },
bg?: number
bg
:
function rgba(r: number, g: number, b: number, a?: number): number
rgba
(30, 30, 40),
transition?: Transition
transition
: {
Transition.duration: number
duration
: 0.3,
Transition.easing?: Easing
easing
: "easeInOut",
Transition.properties: TransitionProperty[]
properties
: ["width", "bg"],
},
});
PropertyTypeDescription
durationnumberDuration in seconds
easing"linear" | "easeIn" | "easeOut" | "easeInOut"Easing function
propertiesTransitionProperty[]Properties to animate
interactivebooleanAllow interaction during transition

Transition properties: "x", "y", "position", "width", "height", "size", "bg", "overlay", "borderColor", "borderWidth", "all".

Renders a text node inside the current open element.

PropertyTypeDescription
colornumberForeground color
bgnumberBackground color
fontSizenumberFont size
fontIdnumberFont identifier
wrapnumberWrap width
attrsnumberText attributes (bold, italic, etc.)

Closes the current open element. Ops must be properly nested. Every open needs a matching close.

Pre-packs an op array into a reusable snapshot op. Useful when part of the UI is static across frames.

import {
function close(): CloseElement
close
,
function open(id: string, props?: Omit<OpenElement, "directive" | "id">): OpenElement
open
,
function snapshot(ops: Op[]): Op
snapshot
,
function text(content: string, props?: Omit<Text, "directive" | "content">): Text
text
,
const grow: (min?: number, max?: number) => SizingAxis
grow
} from "@bomb.sh/tty";
let
let chrome: Op
chrome
=
function snapshot(ops: Op[]): Op
snapshot
([
function open(id: string, props?: Omit<OpenElement, "directive" | "id">): OpenElement
open
("header", {
layout?: {
width?: SizingAxis;
height?: SizingAxis;
padding?: {
left?: number;
right?: number;
top?: number;
bottom?: number;
};
gap?: number;
direction?: "ltr" | "ttb";
alignX?: "left" | "center" | "right";
alignY?: "top" | "center" | "bottom";
}
layout
: {
width?: SizingAxis
width
:
function grow(min?: number, max?: number): SizingAxis
grow
(),
height?: SizingAxis
height
: {
type: "fixed"
type
: "fixed",
value: number
value
: 1 } } }),
function text(content: string, props?: Omit<Text, "directive" | "content">): Text
text
("My App"),
function close(): CloseElement
close
(),
]);
// Each frame: [chrome, ...dynamicOps]

Packs an RGBA color into a 32-bit integer. Alpha defaults to 255.

import {
function rgba(r: number, g: number, b: number, a?: number): number
rgba
} from "@bomb.sh/tty";
function rgba(r: number, g: number, b: number, a?: number): number
rgba
(255, 0, 0); // red
function rgba(r: number, g: number, b: number, a?: number): number
rgba
(0, 255, 0, 128); // semi-transparent green
import {
const fit: (min?: number, max?: number) => SizingAxis
fit
,
const grow: (min?: number, max?: number) => SizingAxis
grow
,
const percent: (value: number) => SizingAxis
percent
,
const fixed: (value: number) => SizingAxis
fixed
} from "@bomb.sh/tty";
function fit(min?: number, max?: number): SizingAxis
fit
(); // shrink to content
function fit(min?: number, max?: number): SizingAxis
fit
(10, 40); // fit with min/max
function grow(min?: number, max?: number): SizingAxis
grow
(); // expand to fill available space
function grow(min?: number, max?: number): SizingAxis
grow
(5, 50); // grow with min/max
function percent(value: number): SizingAxis
percent
(0.5); // 50% of parent
function fixed(value: number): SizingAxis
fixed
(20); // exactly 20 cells
TypeDescription
OpUnion of all directive types
OpenElementElement opened by open()
TextText node
CloseElementElement closed by close()
SizingAxis{ type: "fit" | "grow" | "percent" | "fixed", ... }
TransitionTransition configuration
BorderSidenumber or { width, color?, bg? }
  • Term API (rendering ops to the terminal)
  • Examples (transitions and layout demos)