hakoniwa-term
v0.0.6
Published
A robust, pluggable retro terminal engine for React, powered by CSS Modules and Async Generators.
Maintainers
Readme
hakoniwa-term (箱庭-term)
A programmable, retro hacker-style terminal UI engine for React applications, powered by Async Generators and CSS Modules with total style isolation.
🪴 What is Hakoniwa (箱庭)?
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.
📷 Screenshot & Demo

- Live CodeSandbox Demo: Try on CodeSandbox
✨ Features
- ⚡ 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.
📦 Installation
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.
🚀 Quick Start
- 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>
);
}🎨 Built-in Themes & Customization
hakoniwa-term includes 6 built-in presets out of the box and allows seamless color overrides using the theme prop.
Available Presets
| 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. |
1. Using a Preset
Simply pass the preset prop to the Terminal component:
<Terminal commands={commands} preset="matrix" />2. Customizing Specific Colors (Partial Override)
You can override specific theme colors while keeping the base preset for everything else:
<Terminal
commands={commands}
preset="dracula"
theme={{
prompt: "#00f0ff",
progress: "#ff0055",
}}
/>🧪 Example: hakoniwa-sample-3
https://github.com/user-attachments/assets/14330583-4141-4269-a055-b41c94f8addb
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 dev💡 Advanced Usage: Streaming Logs & Progress Bars
hakoniwa-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.
Example: Multi-step Async Command with Progress Updates
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!",
},
};
},
};🎛️ Component API (TerminalProps)
| 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. |
📐 TypeScript Types & Interfaces
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>;