Skip to main content

duck vim

Tiny, framework-agnostic keyboard command engine with optional React bindings.

What is duck-vim?

duck-vim is a keyboard shortcut engine for the browser. It gives you a registry-based system where shortcuts are declared, not hardcoded. Keybindings can be discovered (via command palettes), customized (per user), and composed (multi-key sequences like g then d) without touching component code.

The core is plain DOM with zero dependencies. React bindings are provided as an optional layer on top.


Why duck-vim?

Most keyboard shortcut libraries solve the simple case (bind Ctrl+K to a function) but fall apart when you need:

  • Multi-key sequences like Vim's g+d (press g, then d within a timeout window)
  • Prefix awareness so the UI can show "waiting for next key..." states
  • Cross-platform Mod key that resolves to Cmd on Mac and Ctrl elsewhere
  • Scoped bindings attached to specific DOM elements, not just document
  • A key recorder for settings UIs where users define their own shortcuts
  • Display formatting that renders Mod+Shift+S as Cmd+Shift+S on Mac and Ctrl+Shift+S on Windows

Architecture

duck-vim is organized into independent modules that compose together:

ModulePurposeFramework dependency
platformOS detection, Mod key resolutionNone
parserParse and validate key binding stringsNone
matcherMatch keyboard events against parsed bindingsNone
commandRegistry + KeyHandler for managing shortcutsNone
sequenceMulti-step sequence managerNone
recorderRecord key combinations for settings UIsNone
formatFormat bindings for display (Cmd+S, Ctrl+S)None
reactProvider, hooks, and context for React appsReact

Loading diagram...



Installation


npm install @gentleduck/vim

npm install @gentleduck/vim
// Core (framework-agnostic)
import { Registry, KeyHandler } from '@gentleduck/vim/command'
import { parseKeyBind } from '@gentleduck/vim/parser'
import { formatForDisplay } from '@gentleduck/vim/format'
 
// React bindings
import { KeyProvider, useKeyBind, useKeySequence } from '@gentleduck/vim/react'
// Core (framework-agnostic)
import { Registry, KeyHandler } from '@gentleduck/vim/command'
import { parseKeyBind } from '@gentleduck/vim/parser'
import { formatForDisplay } from '@gentleduck/vim/format'
 
// React bindings
import { KeyProvider, useKeyBind, useKeySequence } from '@gentleduck/vim/react'

Minimal example

import { Registry, KeyHandler } from '@gentleduck/vim/command'
 
const registry = new Registry()
const handler = new KeyHandler(registry)
 
registry.register('ctrl+k', {
  name: 'Open Palette',
  execute: () => document.getElementById('palette')?.focus(),
})
 
handler.attach(document)
import { Registry, KeyHandler } from '@gentleduck/vim/command'
 
const registry = new Registry()
const handler = new KeyHandler(registry)
 
registry.register('ctrl+k', {
  name: 'Open Palette',
  execute: () => document.getElementById('palette')?.focus(),
})
 
handler.attach(document)