A programmable, retro hacker-style terminal UI engine for React applications, powered by Async Generators and CSS Modules with total style isolation.
In Japanese, 「箱庭(はこにわ / hakoniwa)」 literally means “box garden.”
It refers to a small, self-contained miniature garden or landscape arranged inside a box or tray—like tiny rocks, plants, and buildings forming a little self-contained world you can observe and interact with.
hakoniwa-term brings that miniature, self-contained concept to terminal UIs in React.
- Live CodeSandbox Demo: Try on CodeSandbox
- ⚡ Async Generator Commands: Easily stream logs, output step-by-step responses, or trigger real-time progress bar animations using simple
async function*yield syntax. - 🎨 Built-in Themes & Customization: Comes with 6 pre-built retro presets (
emerald,matrix,dracula,amber,cyberpunk,light) and supports custom color overrides via Dynamic CSS Variables. - 🔒 Style Isolation: Built using CSS Modules—zero CSS leaks into your global styles or main app layout.
- ⏳ Built-in Progress Bar: Stream live
%progress updates directly from command generators. - 🔐 System Locking: Disables input during command execution to prevent race conditions.
- 🧹 Built-in Utilities: Automatic
clearcommand support. - 📘 TypeScript Native: Full type safety for props, logs, yield chunks, presets, and command handlers.
Install hakoniwa-term using your favorite package manager:
npm install hakoniwa-term lucide-react
# or
pnpm add hakoniwa-term lucide-react
# or
yarn add hakoniwa-term lucide-react
Note:
lucide-reactis required for terminal icons.reactandreact-dom(>= 18.0.0) are peer dependencies.
- Import the
Terminalcomponent. - Import the bundled CSS stylesheet (
hakoniwa-term/dist/index.css). - Map your commands using async generator functions.
import React from "react";
import { Terminal } from "hakoniwa-term";
import type { CommandAction } from "hakoniwa-term";
import "hakoniwa-term/dist/index.css";
export default function App() {
const commands: Record<string, CommandAction> = {
// Simple greeting command
hello: async function* (args) {
const name = args[1] || "Guest";
yield {
type: "log",
log: { type: "success", text: `✨ Welcome aboard, ${name}!` },
};
},
// Info command
system: async function* () {
yield {
type: "log",
log: { type: "output", text: "System status: Operational" },
};
yield {
type: "log",
log: { type: "output", text: "Kernel: hakoniwa-v0.0.3" },
};
},
};
return (
<div style={{ padding: "2rem", height: "100vh", background: "#020204" }}>
<Terminal
commands="{commands}"
placeholder="Type 'hello [name]' or 'system'..."
preset="dracula"
promptString="user@hakoniwa:~$ "
title="guest@hakoniwa:~"
/>
</div>
);
}hakoniwa-term includes 6 built-in presets out of the box and allows seamless color overrides using the theme prop.
| Preset | Aesthetic Description |
|---|---|
emerald (default) |
Classic dark theme with vibrant emerald green prompt accents. |
matrix |
Neon green monochrome inspired by 90s cyber aesthetics. |
dracula |
Popular dark purple theme with vibrant pink and cyan highlights. |
amber |
CRT amber monitor aesthetic with warm yellow-orange hues. |
cyberpunk |
High-contrast dark blue with neon cyan, yellow, and magenta accents. |
light |
Clean, modern light mode with high contrast for daytime applications. |
Simply pass the preset prop to the Terminal component:
<Terminal commands={commands} preset="matrix" />You can override specific theme colors while keeping the base preset for everything else:
<Terminal
commands={commands}
preset="dracula"
theme={{
prompt: "#00f0ff",
progress: "#ff0055",
}}
/>hakoniwa-term-preset.mp4
The example/hakoniwa-sample-3 project is an interactive preset playground. Use the preset selector or run these commands in the terminal:
help— show the available commandspreset <name>— switch between the built-in presetsdeploy— stream deployment logs and progress updatesclear— clear the terminal history
It also demonstrates overriding the prompt color with the theme prop. Start it from the repository root with:
cd example/hakoniwa-sample-3
pnpm install
pnpm devhakoniwa-term commands leverage JavaScript Async Generators (async function*). This allows long-running or multi-stage asynchronous tasks to progressively stream log messages and update an integrated progress bar.
import { Terminal } from "hakoniwa-term";
import type { CommandAction } from "hakoniwa-term";
import "hakoniwa-term/dist/index.css";
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
const commands: Record<string, CommandAction> = {
sync: async function* () {
// 1. Stream log output
yield {
type: "log",
log: { type: "output", text: "Connecting to remote repository..." },
};
// 2. Stream progress (20%)
await delay(500);
yield { type: "progress", percent: 20, text: "Fetching remote refs..." };
// 3. Stream progress (65%)
await delay(500);
yield { type: "progress", percent: 65, text: "Unpacking objects..." };
// 4. Stream progress (100%)
await delay(500);
yield {
type: "progress",
percent: 100,
text: "Finalizing sync procedure...",
};
// 5. Stream final success log
yield {
type: "log",
log: {
type: "success",
text: "✨ Repository synchronized successfully!",
},
};
},
errorTest: async function* () {
yield {
type: "log",
log: {
type: "error",
text: "❌ CRITICAL: Unauthorized access detected!",
},
};
},
};| Prop | Type | Default | Description |
|---|---|---|---|
commands |
Record<string, CommandAction> |
Required | Map of command names to async generator handlers. |
promptString |
string |
'user@terminal:~$' |
The prompt prefix displayed before user input. |
placeholder |
string |
'Type a command...' |
Placeholder text for the input box. |
systemLockedText |
string |
'System locked during execution...' |
Placeholder shown while an async command is executing. |
title |
React.ReactNode |
'terminal' |
Header title string or custom React element. |
preset |
TerminalPreset |
'emerald' |
Built-in color preset (emerald, matrix, dracula, amber, cyberpunk, light). |
theme |
Partial<TerminalTheme> |
undefined |
Custom theme object to override specific colors. |
initialHistory |
CommandLog[] |
[] |
Pre-populated log items displayed when mounted. |
showCloseButton |
boolean |
true |
Whether to render the window close button (X). |
onClose |
() => void |
undefined |
Callback invoked when the close button is clicked. |
headerRightActions |
React.ReactNode |
undefined |
Custom React nodes rendered in the top-right of the title bar. |
commandNotFoundFormatter |
(cmd: string) => string |
(cmd) => Command not found: "${cmd}". |
Formatter function for unknown commands. |
export interface CommandLog {
type: "input" | "output" | "error" | "success";
text: string;
}
export type YieldChunk =
| { type: "log"; log: CommandLog }
| { type: "progress"; percent: number; text?: string };
export type CommandAction = (
args: string[],
) => AsyncGenerator<YieldChunk, void, unknown>;
export interface TerminalTheme {
bg: string;
titleBg: string;
border: string;
text: string;
prompt: string;
error: string;
success: string;
progress: string;
}
export type TerminalPreset =
"emerald" | "matrix" | "dracula" | "amber" | "cyberpunk" | "light";
export const TERMINAL_PRESETS: Record<TerminalPreset, TerminalTheme>;