In the previous chapter, Lazy Command Loading, we learned how to efficiently load our command code only when the user asks for it.
Currently, if we ran our session command, it would simply execute a function. But we don't just want to run logic; we want to show the user something beautiful. We want to display a QR Code, a URL, and instructions, all neatly formatted.
If we tried to do this using standard console.log statements, calculating spaces and borders would be a nightmare.
console.logImagine trying to build a website using only a typewriter. You have to manually hit "space" twenty times to center a title. If you change the title, you have to recount the spaces.
That is what building a CLI with console.log feels like.
For the session command, we want a user interface (UI) that looks like this:
The Solution: We use a library called Ink. It allows us to build Terminal UIs using React Components. This works exactly like building a website with HTML, but for the terminal window.
Just like a house is built from bricks, wood, and glass, our CLI interface is built from three main components.
Text: The Content
In HTML, you use <span> or <p> to write text. In our CLI, we use <Text>. It handles the content and the styling (color, bold, dim).
import { Text } from '../../ink.js';
// Renders red, bold text
<Text color="red" bold={true}>
Error: Something went wrong!
</Text>
Box: The Structure
In HTML, you use <div> to group things together. In our CLI, we use <Box>.
Box handles the layout. It decides if items should sit next to each other (row) or on top of each other (column). It also handles margins and padding.
import { Box, Text } from '../../ink.js';
// Renders two text lines, one on top of the other
<Box flexDirection="column" margin={1}>
<Text>Line 1</Text>
<Text>Line 2</Text>
</Box>
Pane: The WrapperThis is a custom component specific to our session project. It acts like the main container for our command's output, ensuring consistent spacing across the entire application.
Let's look at how we build the interface for the session command inside session.tsx. We will construct it piece by piece.
We want a bold title with a little bit of space underneath it.
<Pane>
<Box marginBottom={1}>
<Text bold={true}>Remote session</Text>
</Box>
{/* Rest of the UI follows... */}
</Pane>
Explanation:
<Pane>.<Box> with marginBottom={1} to push the content below it down by one line.<Text bold={true}> to make the title stand out.
A QR code in a terminal is actually just a series of text blocks stacked on top of each other. We receive an array of strings (lines) and map them to <Text> components.
// "lines" is an array of strings representing the QR code
{lines.map((line, i) => (
<Text key={i}>
{line}
</Text>
))}
Explanation:
lines) and output a <Text> component for every line of the QR code.Finally, we want to show the URL and the "Press ESC" hint. We use colors to make it readable.
<Box marginTop={1}>
<Text dimColor={true}>Open in browser: </Text>
<Text color="ide">{remoteSessionUrl}</Text>
</Box>
<Box marginTop={1}>
<Text dimColor={true}>(press esc to close)</Text>
</Box>
Explanation:
dimColor={true} makes the text gray/faint, implying it is secondary information.color="ide" uses a specific theme color defined in our project for URLs.You might be wondering: "How does React, which is a web library, work in a black-and-white terminal window?"
This is the magic of the Ink library.
<Box>).flexDirection, margin, etc.
In our file session.tsx, the function SessionInfo is the main React component.
The call function at the bottom of the file is the bridge between our "Command Configuration" (from Chapter 1) and this UI.
// session.tsx
export const call = async (onDone) => {
// Mount the React component to the terminal
return <SessionInfo onDone={onDone} />;
};
When the command loader runs this:
You have learned how to create Terminal UI Components. Instead of messy string concatenation, we use structured components:
This makes our CLI tool feel professional and easy to read.
However, our session command relies on data that changes. The QR code doesn't appear instantly; it takes a moment to generate. The UI needs to handle "Loading" states and update itself when the data arrives.
How do we handle data that changes over time in a CLI? Let's explore that in the next chapter.
Next Chapter: Reactive State Hook
Generated by Code IQ