Skip to content
Updated April 2026Edit this page ↗

Layout engine

The layout engine computes positions and sizes for terminal widgets using a flexbox-inspired algorithm. You describe a tree of nodes with style constraints, and it figures out where everything goes.

How it works

Build a tree with createLayoutNode(), then pass the root to computeLayout(). The engine modifies each node's .computed rect in place.

TYPESCRIPT
import { createLayoutNode, computeLayout } from '@termuijs/core'
 
// Build the tree
const header  = createLayoutNode('header',  { height: 3 })
const content = createLayoutNode('content', { flexGrow: 1 })
const footer  = createLayoutNode('footer',  { height: 1 })
 
const root = createLayoutNode('root', {
    flexDirection: 'column',
    padding: 1,
}, [header, content, footer])
 
// Compute. mutates .computed on each node
computeLayout(root, 80, 24)
 
console.log(header.computed)
// → { x: 1, y: 1, width: 78, height: 3 }
console.log(content.computed)
// → { x: 1, y: 4, width: 78, height: 18 }
console.log(footer.computed)
// → { x: 1, y: 22, width: 78, height: 1 }

Layout properties

PropertyTypeDefaultDescription
widthnumber | stringFixed width in columns, or percentage like '50%'
heightnumber | stringFixed height in rows, or percentage
paddingnumber | Edges0Inner spacing on all sides
marginnumber | Edges0Outer spacing on all sides
flexDirection'row' | 'column''column'Main axis direction
justifyContent'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around''flex-start'Main axis alignment
alignItems'flex-start' | 'flex-end' | 'center' | 'stretch''stretch'Cross axis alignment
flexGrownumber0How much free space this node should absorb
flexShrinknumber1How much this node shrinks when space is tight
gapnumber0Space between children
borderBorderStyle'none'Border style (takes up space in the layout)
visiblebooleantrueHidden nodes are skipped during layout

Flex grow

Nodes with flexGrow split the remaining space proportionally. A node with flexGrow: 2 gets twice the free space as a node with flexGrow: 1:

TYPESCRIPT
const left  = createLayoutNode('left',  { flexGrow: 1 })
const right = createLayoutNode('right', { flexGrow: 2 })
const row   = createLayoutNode('row', { flexDirection: 'row' }, [left, right])
 
computeLayout(row, 90, 10)
// left.computed.width  → 30  (1/3 of 90)
// right.computed.width → 60  (2/3 of 90)

All computed values are rounded to integers. Terminal cells can't have fractional positions.

Constraint layout

For cases where flexbox isn't a good fit, the splitRect utility divides a rectangle using explicit constraints:

TYPESCRIPT
import { splitRect, length, fill, percentage } from '@termuijs/core'
 
const areas = splitRect(
    { x: 0, y: 0, width: 80, height: 24 },
    'horizontal',
    [length(20), fill(), percentage(25)]
)
// → [{ x:0, width:20 }, { x:20, width:40 }, { x:60, width:20 }]