Skip to main content

Getting Started

Install duck-vim and register your first keyboard shortcut in under 2 minutes.

Installation

npm install @gentleduck/vim
npm install @gentleduck/vim

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)

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?