Getting Started
Install duck-vim and register your first keyboard shortcut in under 2 minutes.
Get up and running with duck-vim in under 2 minutes. Three approaches: vanilla JS, React with hooks, or standalone handlers.
Installation
npm install @gentleduck/vim
npm install @gentleduck/vim
duck-vim ships TypeScript types out of the box. No @types package needed.
Option A: Vanilla (no framework)
This is the simplest path. You create a registry, a handler, register commands, and attach to the DOM.
Create a registry and handler
import { Registry, KeyHandler, type Command } from '@gentleduck/vim/command'
const registry = new Registry()
const handler = new KeyHandler(registry, 600)import { Registry, KeyHandler, type Command } from '@gentleduck/vim/command'
const registry = new Registry()
const handler = new KeyHandler(registry, 600)Define and register commands
const openPalette: Command = {
name: 'Open Command Palette',
execute: () => console.log('Palette opened!'),
}
const goToDashboard: Command = {
name: 'Go to Dashboard',
execute: () => (window.location.href = '/dashboard'),
}
registry.register('ctrl+k', openPalette, { preventDefault: true })
registry.register('g+d', goToDashboard)const openPalette: Command = {
name: 'Open Command Palette',
execute: () => console.log('Palette opened!'),
}
const goToDashboard: Command = {
name: 'Go to Dashboard',
execute: () => (window.location.href = '/dashboard'),
}
registry.register('ctrl+k', openPalette, { preventDefault: true })
registry.register('g+d', goToDashboard)Start listening
handler.attach(document)handler.attach(document)Press Ctrl+K to open the palette. Press g then d (within 600ms) to navigate to the dashboard.
To stop listening:
handler.detach(document)handler.detach(document)Option B: React
Wrap your app in KeyProvider, then use hooks to register bindings.
import { KeyProvider, useKeyBind, useKeySequence } from '@gentleduck/vim/react'
function App() {
const [open, setOpen] = useState(false)
// Single key binding
useKeyBind('ctrl+k', () => setOpen(true), { preventDefault: true })
// Multi-key sequence: press g, then d
useKeySequence(['g', 'd'], () => {
window.location.href = '/dashboard'
})
return (
<div>
<p>Press Ctrl+K to open palette, or g then d to go to dashboard.</p>
{open && <CommandPalette onClose={() => setOpen(false)} />}
</div>
)
}
export default function Root() {
return (
<KeyProvider timeoutMs={600}>
<App />
</KeyProvider>
)
}import { KeyProvider, useKeyBind, useKeySequence } from '@gentleduck/vim/react'
function App() {
const [open, setOpen] = useState(false)
// Single key binding
useKeyBind('ctrl+k', () => setOpen(true), { preventDefault: true })
// Multi-key sequence: press g, then d
useKeySequence(['g', 'd'], () => {
window.location.href = '/dashboard'
})
return (
<div>
<p>Press Ctrl+K to open palette, or g then d to go to dashboard.</p>
{open && <CommandPalette onClose={() => setOpen(false)} />}
</div>
)
}
export default function Root() {
return (
<KeyProvider timeoutMs={600}>
<App />
</KeyProvider>
)
}Option C: Standalone handlers (no registry)
If you just need a quick one-off binding without a full registry, use createKeyBindHandler.
import { createKeyBindHandler } from '@gentleduck/vim/matcher'
const handler = createKeyBindHandler({
binding: 'Mod+S',
handler: (e) => {
console.log('Save!')
},
options: { preventDefault: true },
})
document.addEventListener('keydown', handler)import { createKeyBindHandler } from '@gentleduck/vim/matcher'
const handler = createKeyBindHandler({
binding: 'Mod+S',
handler: (e) => {
console.log('Save!')
},
options: { preventDefault: true },
})
document.addEventListener('keydown', handler)This resolves Mod to Cmd on Mac and Ctrl on Windows/Linux automatically.
What's next?
- Read Core Concepts to understand key descriptors, sequences, and the Mod key.
- Browse the API Reference for every module.
- Follow the Course for a hands-on walkthrough from beginner to advanced.