How to Configure Vim-Style Keyboard Navigation Mappings in Claudian
Claudian implements a dedicated Vim-style navigation layer that lets you scroll chat history and focus the input box using single-character key bindings, customizable through a simple map <key> <action> syntax.
Claudian is an open-source chat interface that brings Vim-style keyboard navigation mappings to AI conversations. The implementation spans three core modules that handle settings persistence, text parsing, and real-time DOM event handling, allowing you to navigate entirely without touching the mouse.
How Vim-Style Navigation Works in Claudian
The navigation system relies on a coordinated trio of components that transform user-defined text mappings into live keyboard shortcuts.
The Settings Layer
Global defaults reside in src/core/types/settings.ts (lines 338-344), where the KeyboardNavigationSettings interface defines three configurable keys:
scrollUpKey: Default"w"scrollDownKey: Default"s"focusInputKey: Default"i"
These values initialize the navigation state when Claudian loads.
The Mapping Parser
The src/features/settings/keyboardNavigation.ts file (lines 6-60) contains the serialization logic. The buildNavMappingText function converts settings objects into human-readable text blocks, while parseNavMappings validates user input. The parser enforces strict rules: each line must follow map <key> <action>, keys must be single characters, and duplicates are rejected.
The Runtime Controller
NavigationController in src/features/chat/controllers/NavigationController.ts (lines 80-107) registers DOM listeners with capture: true to intercept keystrokes before they bubble. It normalizes keys using e.key.toLowerCase(), compares them against the current settings object, and triggers either smooth scrolling via requestAnimationFrame or focuses the textarea. It also handles the Escape key to exit insert mode, completing the Vim workflow.
Configuring Your Keyboard Mappings
You can customize bindings through three methods, all using the same map <key> <action> format where valid actions are scrollUp, scrollDown, and focusInput.
Method 1: Programmatic Configuration
Use the parseNavMappings utility to validate and apply mappings from code:
import { parseNavMappings } from '@/features/settings/keyboardNavigation';
// Define custom Vim bindings (H for up, J for down, Enter to insert)
const mappingText = `
map h scrollUp
map j scrollDown
map Enter focusInput
`;
const { settings, error } = parseNavMappings(mappingText);
if (error) {
console.error('Validation failed:', error);
} else {
// Persist to Claudian's data layer
await this.saveData({ keyboardNavigation: settings });
}
The function returns a KeyboardNavigationSettings object only if all lines pass validation.
Method 2: Direct settings.json Editing
For manual configuration, edit your settings.json file directly:
{
"keyboardNavigation": {
"scrollUpKey": "h",
"scrollDownKey": "j",
"focusInputKey": "Enter"
}
}
Claudian reloads these values on startup, and NavigationController picks them up via its getSettings callback.
Method 3: Generating Mapping Text
To inspect or debug current mappings, use buildNavMappingText to serialize your settings:
import { buildNavMappingText } from '@/features/settings/keyboardNavigation';
import type { KeyboardNavigationSettings } from '@/core/types/settings';
const current: KeyboardNavigationSettings = {
scrollUpKey: 'h',
scrollDownKey: 'j',
focusInputKey: 'Enter',
};
const block = buildNavMappingText(current);
console.log(block);
// Output:
// map h scrollUp
// map j scrollDown
// map Enter focusInput
Extending Navigation Actions (Advanced)
To add custom actions beyond the three defaults, modify the NAV_ACTIONS constant in src/features/settings/keyboardNavigation.ts and add corresponding logic in NavigationController.handleMessagesKeydown:
// In keyboardNavigation.ts
const NAV_ACTIONS = ['scrollUp', 'scrollDown', 'focusInput', 'jumpToEnd'] as const;
// In NavigationController.ts
if (key === settings.jumpToEndKey?.toLowerCase()) {
e.preventDefault();
const messagesEl = this.deps.getMessagesEl();
messagesEl.scrollTop = messagesEl.scrollHeight;
return;
}
You must also add jumpToEndKey to the KeyboardNavigationSettings type definition and default values in src/core/types/settings.ts.
Summary
- Vim-style navigation in Claudian uses single-character mappings for scroll and focus actions, defaulting to
w/sfor scrolling andifor insert mode. - Configuration storage lives in
src/core/types/settings.ts, with runtime handling inNavigationController.ts. - Text-based mappings follow Vim's
map <key> <action>syntax and are parsed byparseNavMappingsinsrc/features/settings/keyboardNavigation.ts. - Validation ensures unique, single-character keys and known actions only.
- Advanced users can extend
NAV_ACTIONSto add custom navigation behaviors like jumping to conversation end.
Frequently Asked Questions
What are the default vim-style keys in Claudian?
By default, Claudian uses w to scroll up, s to scroll down, and i to focus the input textarea (mimicking Vim's insert mode). These defaults are defined in src/core/types/settings.ts and can be overridden through any configuration method.
How do I disable vim-style navigation entirely?
While there is no explicit "disable" toggle, you can effectively disable the feature by mapping the scroll and focus actions to impossible keys (such as empty strings or unused function keys) in your settings.json, or by ensuring no map entries exist in the navigation settings block, which prevents NavigationController from matching any keystrokes.
Can I use multi-character keys or key combinations for navigation?
No. According to the validation logic in parseNavMappings, each key must be a single character. The implementation in NavigationController compares the pressed key directly against single-character strings in settings.scrollUpKey, settings.scrollDownKey, and settings.focusInputKey, making multi-character sequences or modifiers like Ctrl unsupported in the current architecture.
Why aren't my new keyboard mappings working immediately?
Changes require reloading the settings object that NavigationController consumes via its getSettings callback. If editing settings.json, restart the plugin or trigger a settings refresh. If using the programmatic API, ensure you call saveData to persist the new KeyboardNavigationSettings object so the controller receives the updated values on its next settings poll.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →