inkjs-design
Ink.js Design
Comprehensive guide for building terminal UIs with Ink.js (React for CLI).
Quick Start
Creating a New Component
- Determine component type: Screen / Part / Common
- Reference component-patterns.md for similar patterns
- Add type definitions
- Implement component
- Write tests
Common Issues & Solutions
| Issue | Reference |
|---|---|
| Emoji width misalignment | ink-gotchas.md |
| Ctrl+C called twice | ink-gotchas.md |
| useInput conflicts | ink-gotchas.md |
| Layout breaking | responsive-layout.md |
| Screen navigation | multi-screen-navigation.md |
Directory Conventions
src/cli/ui/
├── components/
│ ├── App.tsx # Root component with screen management
│ ├── common/ # Common input components (Select, Input)
│ ├── parts/ # Reusable UI parts (Header, Footer)
│ └── screens/ # Full-screen components
├── hooks/ # Custom hooks
├── utils/ # Utility functions
└── types.ts # Type definitions
Component Classification
Screen (Full-page views)
- Represents a complete screen/page
- Handles keyboard input via
useInput - Implements Header/Content/Footer layout
- Manages screen-level state
Part (Reusable elements)
- Reusable UI building blocks
- Optimized with
React.memo - Stateless/pure components preferred
- Accept configuration via props
Common (Input components)
- Basic input components
- Support both controlled and uncontrolled modes
- Handle focus management
- Provide consistent UX
Essential Patterns
1. Icon Width Override
Fix string-width v8 emoji width calculation issues:
const WIDTH_OVERRIDES: Record<string, number> = {
"⚡": 1, "✨": 1, "🐛": 1, "🔥": 1, "🚀": 1,
"🟢": 1, "🟠": 1, "✅": 1, "⚠️": 1,
};
const getIconWidth = (icon: string): number => {
const baseWidth = stringWidth(icon);
const override = WIDTH_OVERRIDES[icon];
return override !== undefined ? Math.max(baseWidth, override) : baseWidth;
};
2. useInput Conflict Avoidance
Multiple useInput hooks all fire - use early return or isActive:
useInput((input, key) => {
if (disabled) return; // Early return when inactive
// Handle input...
}, { isActive: isFocused });
3. Ctrl+C Handling
render(<App />, { exitOnCtrlC: false });
// In component
const { exit } = useApp();
useInput((input, key) => {
if (key.ctrl && input === "c") {
cleanup();
exit();
}
});
4. Dynamic Height Calculation
const { rows } = useTerminalSize();
const HEADER_LINES = 3;
const FOOTER_LINES = 2;
const contentHeight = rows - HEADER_LINES - FOOTER_LINES;
const visibleItems = Math.max(5, contentHeight);
5. React.memo with Custom Comparator
function arePropsEqual<T>(prev: Props<T>, next: Props<T>): boolean {
if (prev.items.length !== next.items.length) return false;
for (let i = 0; i < prev.items.length; i++) {
if (prev.items[i].value !== next.items[i].value) return false;
}
return prev.selectedIndex === next.selectedIndex;
}
export const Select = React.memo(SelectComponent, arePropsEqual);
6. Multi-Screen Navigation
type ScreenType = "main" | "detail" | "settings";
const [screenStack, setScreenStack] = useState<ScreenType[]>(["main"]);
const currentScreen = screenStack[screenStack.length - 1];
const navigateTo = (screen: ScreenType) => {
setScreenStack(prev => [...prev, screen]);
};
const goBack = () => {
if (screenStack.length > 1) {
setScreenStack(prev => prev.slice(0, -1));
}
};
Detailed References
Core Patterns
- Component Patterns - Screen/Part/Common architecture
- Hooks Guide - Custom hook design patterns
Advanced Topics
- Multi-Screen Navigation - Screen stack management
- Animation Patterns - Spinners and progress bars
- State Management - Complex state patterns
- Responsive Layout - Terminal size handling
- Performance Optimization - Optimization techniques
- Input Handling - Keyboard input patterns
Troubleshooting
- Ink Gotchas - Common issues and solutions
- Testing Patterns - ink-testing-library usage
Examples
See examples/ for practical implementation examples.
More from akiojin/skills
opentui-design
Comprehensive toolkit for designing and implementing CLI applications with OpenTUI and SolidJS. Use when building CLI screens/components, debugging input handling, implementing screen navigation, handling mouse events, or optimizing CLI performance.
34drawio
Create and edit draw.io diagrams in XML format. Use when the user wants to create flowcharts, architecture diagrams, sequence diagrams, or any visual diagrams. Handles XML structure, styling, fonts (Noto Sans JP), arrows, connectors, and PNG export.
22skills-repo-maintenance
Add or update skills in a skills repository for Codex and/or Claude Code. Use when creating new skills, packaging .skill files for Codex, or converting a skill into a Claude Code plugin (marketplace.json + plugin.json).
16gh-pr
Create or update GitHub Pull Requests with the gh CLI, including deciding whether to create a new PR or only push based on existing PR merge status. Use when the user asks to open/create/edit a PR, generate a PR body/template, or says 'PRを出して/PR作成/gh pr'. Defaults: base=develop, head=current branch (same-branch only; never create/switch branches).
12speckit-require
GitHub Spec Kit (https://github.com/github/spec-kit) を使って要件定義や仕様作成(仕様策定・仕様書作成・仕様設計を含む)を新規作成または既存仕様へ追記し、spec.md/plan.md/tasks.mdまで生成・更新する。要件定義、要件追加/変更、TDD前提の要件整理、仕様の明文化、Spec Kitのspecify/clarify/plan/tasksフロー実行が求められるときに使用。
12speckit-update
GitHub Spec Kit (https://github.com/github/spec-kit) のベースバージョン更新やテンプレート/スクリプト同期を行うための手順。Spec Kitの更新、上流リリースとの差分適用、templates/commands/scriptsの取り込み、ローカル運用(日本語化・ブランチ非操作・SPEC-[UUID8桁])の維持が必要なときに使用する。
11