Welcome back! In the previous chapter, Ink UI Components, we learned how to build layouts using <Box> and <Text>.
You might have noticed that in our Plugin Hint System, we wrapped all our code inside a component called <PermissionDialog>.
In this chapter, we are going to look at The Frame. We will learn how to create a consistent, reusable wrapper that makes your CLI tools look professional and trustworthy.
Imagine you are using an app on your phone, and it wants to access your camera. You don't see a button drawn by the app itself; you see a standard System Popup provided by the OS (iOS or Android).
Why?
In a Command Line Interface (CLI), we face the same challenge. If we just print text, it gets lost in the logs. We need a standard Permission Dialog Wrapper to frame our questions.
This component is actually quite simple. It relies on a specific feature of Ink: Borders.
The Wrapper is a box that holds other content. In React, we call the content inside a component its children.
We draw a line around the content. This creates a visual "island" in the terminal.
We embed a title (like "Security Alert" or "Plugin Recommendation") directly into the border frame.
Let's say you want to ask the user for permission to delete a file. Instead of just printing text, we wrap it in our dialog.
Here is how you use it in your code:
import { PermissionDialog } from './permissions/PermissionDialog.js';
import { Text } from 'ink';
// Inside your component
<PermissionDialog title="Security Warning">
<Text>
Are you sure you want to delete database.sqlite?
</Text>
</PermissionDialog>
What the User Sees:
The code above produces a neat box with rounded corners:
โญโ Security Warning โโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ โ
โ Are you sure you want to delete โ
โ database.sqlite? โ
โ โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
This draws attention immediately!
How does this wrapper actually work? Let's trace what happens when you use <PermissionDialog>.
The component acts as a Passthrough. It doesn't care what is inside it (text, menus, spinners). It just provides the frame.
Let's build the PermissionDialog.tsx file. We will use the Ink UI Components we learned earlier.
We need to know the Title of the dialog, and we need to accept the content (children) to put inside.
import React from 'react';
import { Box } from 'ink';
type Props = {
title: string;
children: React.ReactNode;
};
Explanation: React.ReactNode is the standard type for "anything that React can render" (text, other components, etc.).
We use the standard <Box> component from Ink, but we add specific styling props to create the border.
export function PermissionDialog({ title, children }: Props) {
return (
<Box
borderStyle="round"
borderColor="gray"
title={title}
paddingX={1}
>
{children}
</Box>
);
}
Explanation:
borderStyle="round": This tells Ink to draw the box using rounded corner characters (โญ, โฎ, โฏ, โฐ).borderColor="gray": We keep the color subtle so it looks like a system prompt, not an error (red) or success (green) message.title={title}: Ink knows how to embed a string into the top border of the box automatically.{children}: This is where your specific content (like the text or the "Yes/No" menu) gets injected.
You might ask: "Why create a specific component? Why not just write <Box borderStyle="round"> every time?"
PermissionDialog.tsx), and every prompt in your app updates automatically.borderStyle you chose or how much padding you used. You just import PermissionDialog and it "just works."You have learned how to create a Permission Dialog Wrapper.
By creating this simple component, you have ensured that:
Now that we have a nice frame, we need a way for the user to answer the questions inside it. In the next chapter, we will build a robust way to handle user choices.
Next Chapter: Custom Selection Input
Generated by Code IQ