Welcome to Chapter 4! In the previous chapter, Usage & Quota Monitoring, we focused on fetching data and handling errors. We ended up with some numbersβlike knowing you have used 80% of your quota.
But looking at raw numbers isn't very exciting. We want to visualize it.
console.log
If you have written a basic script before, you probably used console.log("Hello") to output text. This works for simple logs, but it has limitations:
The Problem: We want our Settings app to look like a real application with a header, tabs, side-by-side columns, and progress bars. We want to treat the terminal like a graphical canvas, not a typewriter.
The Solution:
We use a library called Ink. Ink allows us to write React components (just like a website), but instead of rendering HTML (like <div> or <h1>), it renders text and layout commands to the terminal.
To build a UI in Ink, you only need to master two main components and one layout system.
<Text> Component
Think of this as the <span> or <p> tag of the terminal. It handles the content and the styling (color, bold, underline).
<span style="color: green">Success</span><Text color="green">Success</Text><Box> Component
Think of this as the <div> tag. It is invisible by default. Its only job is to hold other components and decide how they are arranged.
This is the "glue". Ink uses the CSS Flexbox model.
Let's build the Usage Bar we saw in the previous chapter. We want to turn a number (like 0.5 or 50%) into a visual bar: ββββββββββ.
First, let's just render the label.
import { Text } from 'ink';
// Simple text rendering
<Text bold>Current Session Usage:</Text>
We want the label to be above the bar. We need a "Column" layout.
import { Box, Text } from 'ink';
<Box flexDirection="column">
<Text bold>Current Session Usage:</Text>
<Text> [Bar goes here] </Text>
</Box>
flexDirection="column" tells the Box: "Put the first child on top, and the second child below it."What if we want the label on the left and the value on the right? We change the direction to "row".
<Box flexDirection="row" justifyContent="space-between">
<Text>Usage:</Text>
<Text>50%</Text>
</Box>
justifyContent="space-between" pushes the two text items to the far edges of the terminal window.How does writing React code result in a terminal UI? It involves a process called Reconciliation.
Let's look at the actual code for the LimitBar we used in Usage & Quota Monitoring.
In Usage.tsx, we combine these concepts to create the usage visualization.
// Inside Usage.tsx -> LimitBar function
if (maxWidth >= 62) {
return (
<Box flexDirection="column">
{/* 1. Title on top */}
<Text bold={true}>{title}</Text>
{/* 2. Bar and Text side-by-side below */}
<Box flexDirection="row" gap={1}>
<ProgressBar ratio={utilization / 100} width={50} />
<Text>{Math.floor(utilization)}% used</Text>
</Box>
</Box>
);
}
<Box> (column) to stack the Title above the content.<Box> (row) to put the <ProgressBar> and the "50% used" text next to each other.gap={1} adds exactly one character of space between the bar and the text.
We often create custom components to handle visual logic. The ProgressBar (imported from design-system) does the math to determine how many "filled" blocks vs "empty" blocks to draw.
Note: This logic is simplified for understanding.
function ProgressBar({ ratio, width }) {
// Calculate how many filled blocks we need
const filledCount = Math.floor(ratio * width);
const emptyCount = width - filledCount;
// Create the strings
const filled = 'β'.repeat(filledCount);
const empty = 'β'.repeat(emptyCount);
return (
<Text>
<Text color="green">{filled}</Text>
<Text color="gray">{empty}</Text>
</Text>
);
}
width is 10 and ratio is 0.5, filledCount is 5.β character 5 times.<Text> tag so they appear on the same line.
In Usage.tsx, we also see this hook:
const { columns } = useTerminalSize();
const availableWidth = columns - 2;
const maxWidth = Math.min(availableWidth, 80);
useTerminalSize.columns changes, the component re-renders, and the <ProgressBar> automatically adjusts its width.In this chapter, we learned how to paint our application:
<Text> for content and styling.<Box> with Flexbox for layout (Rows vs Columns).We now have a Settings container (Chapter 1), populated with data (Chapters 2 & 3), and beautifully rendered (Chapter 4).
However, a pretty interface is useless if you can't control it. How do we switch tabs? How do we close the modal?
Next Chapter: Keybinding & Interaction System
Generated by Code IQ