In the previous chapter, Core Component Primitives, we learned how to compose UI using <Box> and <Text>.
But here is the mystery: The terminal is just a grid of characters. It doesn't know what a "Flexbox" is. It doesn't know what a "component" is. It doesn't even know what a "parent" or "child" is.
So, how does Ink know that justifyContent="center" means "calculate the width of the window, subtract the width of the content, divide by two, and place the cursor there"?
The answer is the Ink DOM and the Yoga Layout Engine.
Imagine you are building a house. Before you paint the walls (render pixels), you need a blueprint and a wooden frame (DOM) to hold everything together.
Browsers have the HTML DOM (<div>, <span>).
Ink creates its own DOM (Document Object Model) solely in memory.
Let's look at this simple layout:
<Box height={10} alignItems="center" justifyContent="center">
<Text>I am centered!</Text>
</Box>
To make this work, Ink needs to perform three invisible steps:
<Text> is inside <Box>.
In a browser, you have HTMLElement. In Ink, we have DOMElement.
An InkNode (or DOMElement) is a plain JavaScript object that represents one piece of your UI. It acts as a container for:
If you could inspect Ink's memory while running the example above, a simplified ink-box node would look like this:
// A simplified view of an Ink Node
const boxNode = {
nodeName: 'ink-box',
parentNode: rootNode,
childNodes: [textNode], // The "I am centered" node
style: { alignItems: 'center' },
// The layout engine instance
yogaNode: <YogaNode Instance>
};
Terminals don't come with CSS layout engines. Browsers do. To bridge this gap, Ink uses Yoga.
Yoga is a layout engine written in C++ (and compiled to JavaScript) that implements Flexbox. It does the heavy math.
Think of Yoga as a calculator:
When your React code runs, it talks to the Ink DOM, which talks to Yoga.
Let's look under the hood at how Ink implements this "Invisible Skeleton."
dom.ts)
When Ink initializes a component, it creates a DOMElement. Notice how it initializes the yogaNode immediately.
// dom.ts (Simplified)
import { createLayoutNode } from './layout/engine.js';
export const createNode = (nodeName) => {
return {
nodeName,
childNodes: [],
style: {},
// Every visual element gets a Yoga node for math
yogaNode: createLayoutNode(),
dirty: false
};
};
Explanation:
nodeName: Keeps track of what this is (ink-box, ink-text).yogaNode: This is the bridge to the C++ engine.styles.ts)
When you write <Box margin={1}>, Ink has to translate that React prop into a Yoga instruction.
// styles.ts (Simplified)
const applyMarginStyles = (node, style) => {
// If the user set a margin prop...
if ('margin' in style) {
// ...tell Yoga to apply it to All sides.
// LayoutEdge.All maps to Yoga.EDGE_ALL
node.setMargin(LayoutEdge.All, style.margin ?? 0);
}
};
Explanation:
margin) and calls the strict Yoga setter (setMargin).layout/yoga.ts)Ink wraps the raw Yoga library to make it safer and easier to use with TypeScript.
// layout/yoga.ts (Simplified)
export class YogaLayoutNode {
constructor(yogaNode) {
this.yoga = yogaNode;
}
calculateLayout(width, height) {
// Trigger the heavy C++ calculation
this.yoga.calculateLayout(width, height, Direction.LTR);
}
getComputedLeft() {
// Retrieve the calculated X position
return this.yoga.getComputedLeft();
}
}
Explanation:
calculateLayout: The trigger button. It cascades down the tree, calculating positions for the node and all its children.getComputedLeft: After calculation, this tells us where to draw the element.Recalling our example from Chapter 1:
<Box borderStyle="single">
<Text>Status: OK</Text>
</Box>
Here is the lifecycle in the Ink DOM:
ink-box (The Box).ink-text (The Text).borderStyle="single".styles.ts sees this and tells Yoga: node.setBorder(Edge.All, 1). Use 1 unit of space for the border.Now the "Skeleton" is complete. Every node knows its X, Y, Width, and Height.
In this chapter, we learned:
ink-box, ink-text) representing your UI.styles.ts translates your props into Yoga commands.We have a structure. We have positions. But how does React manage updates to this structure when data changes?
Next Chapter: React Reconciler
Generated by Code IQ