Welcome to the world of Ink! If you have ever tried to style text in a terminal using raw codes like \x1b[31m, you know it can be a headache.
Ink allows you to build Command Line Interfaces (CLIs) using React. Just like building a website, you compose your UI using components.
In this chapter, we will explore the two building blocks of every Ink application:
<Text>: Used for rendering strings and applying styles (colors, bold, etc.).<Box>: Used for layout (margins, padding, borders, and positioning).To understand these components, let's build a simple UI element: a box that tells the user the server is running successfully.
It should look something like this in your terminal:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ Server running on port 80 โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
<Text> Component
In standard HTML, you wrap text in <span> or <p> tags to style it. In Ink, you use the <Text> component.
The <Text> component is responsible for styling characters. It handles colors, background colors, and modifiers like bold or italic.
Here is how you make text green and bold:
import { Text } from 'ink';
const Status = () => (
<Text color="green" bold>
โ Server running on port 80
</Text>
);
What happens here?
Ink takes the string โ Server running on port 80, applies the ANSI color code for green, applies the code for bold, and prints it to the terminal.
Terminals have a fixed width. If your text is too long, <Text> helps you control how it breaks.
<Text wrap="truncate-end">
This is a very long path that might not fit...
</Text>
This prevents your CLI from looking broken when a window is resized.
<Box> Component
Now that we have styled text, we need to position it. In HTML, you use <div>. In Ink, you use <Box>.
The <Box> component is the container. It uses Flexbox (just like CSS) to arrange its children. It handles:
Let's put our text inside a Box to create the card effect.
import { Box, Text } from 'ink';
const Card = () => (
<Box borderStyle="single" paddingX={1}>
<Text color="green">โ Server running</Text>
</Box>
);
Explanation:
borderStyle="single" draws the lines around the content.paddingX={1} adds 1 space of gap on the left and right inside the border.
By default, a <Box> arranges children in a Row (left to right). You can change this to a Column (top to bottom).
<Box flexDirection="column" borderStyle="round">
<Text color="green">Status: Online</Text>
<Text color="blue">Port: 8080</Text>
</Box>
This stacks the two lines of text vertically inside a rounded box.
Let's combine everything to build our final "Server Status" component.
import React from 'react';
import { Box, Text } from 'ink';
const ServerStatus = () => (
<Box borderStyle="single" borderColor="green">
<Box paddingX={2}>
<Text color="green" bold>โ Success:</Text>
</Box>
<Text>Server running on localhost:3000</Text>
</Box>
);
Visualizing the Output: This creates a green-bordered box. Inside, on the left, "โ Success:" appears in bold green (with some padding). Immediately following it is the white text description.
You might be wondering: How does a React component turn into a terminal layout?
Ink uses a fascinating translation layer. It doesn't use the browser's DOM. Instead, it creates a custom "Ink DOM" and passes style instructions to a layout engine.
<Box> function.children) from style props (like margin, color).styles.ts) translates these props into commands for the layout engine.styles.ts
The file styles.ts is the dictionary that translates Ink props into layout commands.
When you write <Box margin={1} />, Ink calls a function like this internally:
// Inside styles.ts (Simplified)
const applyMarginStyles = (node: LayoutNode, style: Styles): void => {
// If "margin" prop exists...
if ('margin' in style) {
// Tell the underlying layout engine to set margin on all sides
node.setMargin(LayoutEdge.All, style.margin ?? 0)
}
}
This directly manipulates the Yoga layout node (the engine Ink uses for Flexbox math).
Text.tsx
The <Text> component is essentially a filter. It creates an internal <ink-text> element and attaches style metadata.
// Inside components/Text.tsx (Simplified)
export default function Text({ color, bold, children }: Props) {
// 1. Group style props together
const textStyles = {
color,
bold
};
// 2. Render an internal primitive with these styles
return (
<ink-text textStyles={textStyles}>
{children}
</ink-text>
);
}
Unlike HTML, <ink-text> is not a real DOM tag. It is a signal to the Ink Reconciler (which we will discuss in React Reconciler).
Box.tsx
The <Box> component is the heavy lifter. It gathers layout styles and passes them down.
// Inside components/Box.tsx (Simplified)
function Box({ children, style, flexDirection = 'row' }) {
// 1. Merge default styles with user styles
const finalStyle = {
flexDirection,
...style
};
// 2. Render the internal primitive
return (
<ink-box style={finalStyle}>
{children}
</ink-box>
);
}
Notice <ink-box>. This internal element holds the configuration that eventually determines exactly which cell (row/column) in the terminal a character will appear in.
In this chapter, you learned:
<Text> is for styling strings (color, bold).<Box> is for layout (flexbox, borders, spacing).styles.ts.But how does Ink actually know where to put the cursor based on these boxes?
Next Chapter: Ink DOM & Layout Engine
Generated by Code IQ