Getting Started
A framework for building interactive terminal user interfaces with React and Ink
giggles is a framework for building interactive terminal user interfaces (TUIs) with React and Ink. It provides a declarative, composable system for handling focus navigation and keyboard input in terminal applications.
Installation
Install giggles along with its peer dependencies:
npm install giggles ink reactpnpm add giggles ink reactyarn add giggles ink reactVerify requirements:
- Node.js 16+
- React 18+ or 19+
- Ink 6+
Your First TUI
Here's a simple focusable menu that demonstrates giggles' core concepts:
import { Box, Text, render } from 'ink';
import { GigglesProvider, FocusGroup, useFocus, useKeybindings } from 'giggles';
function MenuItem({ label }: { label: string }) {
const focus = useFocus();
return (
<Box>
<Text color={focus.isFocused ? 'green' : 'white'}>
{focus.isFocused ? '> ' : ' '}
{label}
</Text>
</Box>
);
}
function Menu() {
const focus = useFocus();
useKeybindings(focus, {
q: () => process.exit(0),
});
return (
<Box flexDirection="column">
<Text bold>My Menu (j/k to navigate, q to quit)</Text>
<Text> </Text>
<FocusGroup direction="vertical">
<MenuItem label="Start Game" />
<MenuItem label="Settings" />
<MenuItem label="Exit" />
</FocusGroup>
</Box>
);
}
function App() {
return (
<GigglesProvider>
<Menu />
</GigglesProvider>
);
}
render(<App />);What's happening
GigglesProviderwraps your app and provides focus + input contextFocusGroupcreates a navigable group (usej/kor arrow keys)useFocus()gives each item access to its focus stateuseKeybindings()binds keys to actions
Core Concepts
Focus System
giggles manages focus as a tree structure. Components can be focusable, and FocusGroup creates navigable lists. Focus moves through the tree based on your layout.
- FocusGroup: Container for navigable items (horizontal or vertical)
- useFocus(): Hook to access focus state and methods
- useFocusState(): Observer hook for focus changes
Input Handling
Input events flow through the focus tree. The focused component receives input first, then it bubbles up the tree until handled.
- useKeybindings(): Bind keys to actions for a component
- FocusTrap: Contain input within a section (for modals, dialogs)
- Key normalization:
ctrl+c,Ctrl-C,^call work the same