---
name: inkjs-design
description: |
  Ink.js (React for CLI) design and implementation guide.
  Use when:
  (1) Creating or modifying Ink.js components
  (2) Implementing Ink-specific hooks (useInput, useApp, useFocus)
  (3) Handling emoji/icon width issues (string-width workarounds)
  (4) Building terminal-responsive layouts
  (5) Managing multi-screen navigation
  (6) Implementing animations (spinners, progress bars)
  (7) Optimizing performance (React.memo, useMemo)
  (8) Handling keyboard input and shortcuts
  (9) Testing CLI UI (ink-testing-library)
---

# Ink.js Design

Comprehensive guide for building terminal UIs with Ink.js (React for CLI).

## Quick Start

### Creating a New Component

1. Determine component type: Screen / Part / Common
2. Reference [component-patterns.md](references/component-patterns.md) for similar patterns
3. Add type definitions
4. Implement component
5. Write tests

### Common Issues & Solutions

| Issue | Reference |
|-------|-----------|
| Emoji width misalignment | [ink-gotchas.md](references/ink-gotchas.md#icon-emoji-width) |
| Ctrl+C called twice | [ink-gotchas.md](references/ink-gotchas.md#ctrl-c-handling) |
| useInput conflicts | [ink-gotchas.md](references/ink-gotchas.md#useinput-conflicts) |
| Layout breaking | [responsive-layout.md](references/responsive-layout.md) |
| Screen navigation | [multi-screen-navigation.md](references/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:

```typescript
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:

```typescript
useInput((input, key) => {
  if (disabled) return;  // Early return when inactive
  // Handle input...
}, { isActive: isFocused });
```

### 3. Ctrl+C Handling

```typescript
render(<App />, { exitOnCtrlC: false });

// In component
const { exit } = useApp();
useInput((input, key) => {
  if (key.ctrl && input === "c") {
    cleanup();
    exit();
  }
});
```

### 4. Dynamic Height Calculation

```typescript
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

```typescript
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

```typescript
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](references/component-patterns.md) - Screen/Part/Common architecture
- [Hooks Guide](references/hooks-guide.md) - Custom hook design patterns

### Advanced Topics
- [Multi-Screen Navigation](references/multi-screen-navigation.md) - Screen stack management
- [Animation Patterns](references/animation-patterns.md) - Spinners and progress bars
- [State Management](references/state-management.md) - Complex state patterns
- [Responsive Layout](references/responsive-layout.md) - Terminal size handling
- [Performance Optimization](references/performance-optimization.md) - Optimization techniques
- [Input Handling](references/input-handling.md) - Keyboard input patterns

### Troubleshooting
- [Ink Gotchas](references/ink-gotchas.md) - Common issues and solutions
- [Testing Patterns](references/testing-patterns.md) - ink-testing-library usage

## Examples

See [examples/](examples/) for practical implementation examples.
