Skip to content

Keys

Composable, scoped keyboard navigation hooks for React >=19.2.0.

Composable, scoped keyboard navigation hooks for React >=19.2.0.

  • Zero runtime dependencies
  • Scope stack -- only the topmost scope receives events, no conflicts between layers
  • React >=19.2.0 ready -- stable callback/ref patterns without stale closures
  • ESM only
Info:

Note: Diffgazer packages are not yet published to npm. Until the first release, pack @diffgazer/ui and @diffgazer/keys from the repository and install those tarballs.

Installation options

@diffgazer/ui components are not all visual-only. Some copied components include local keyboard handling, and some package-mode entries import hooks from @diffgazer/keys. You have four ways to source keyboard hooks:

Option 1: dgadd component install

When you install a @diffgazer/ui component that requires keyboard hooks via dgadd, choose whether to copy the standalone hooks or import the @diffgazer/keys package:

bash
pnpm exec dgadd add ui/menu --integration copy
pnpm exec dgadd add ui/menu --integration keys

This works from bundled registry metadata. Components that reference keyboard hooks declare the keys hook dependency, and dgadd installs the matching integration before writing files.

You can also install hooks standalone:

bash
pnpm exec dgadd add keys/navigation
pnpm exec dgadd add keys/focus-trap
pnpm exec dgadd add keys/focus-restore
pnpm exec dgadd add keys/scroll-lock

Option 2: Install @diffgazer/keys package

Install the package. Before publication it is not on npm, so pack it from a checkout of the repository and install that tarball (Consumption Paths):

bash
pnpm --filter @diffgazer/keys pack --pack-destination /tmp/diffgazer-packs
npm install /tmp/diffgazer-packs/diffgazer-keys-*.tgz

After publication, this becomes:

bash
npm install @diffgazer/keys

You can also let dgadd set up keys integration:

bash
pnpm exec dgadd add ui/menu --integration keys

Components import hooks directly from the @diffgazer/keys package. Gives you the full API including KeyboardProvider, useKey, useScope, and useFocusZone.

Option 3: Copy standalone hooks

bash
pnpm exec dgadd add keys/navigation keys/focus-trap keys/focus-restore keys/scroll-lock
pnpm exec dgadd add ui/menu --integration copy

Copies standalone hooks into your project at @/hooks/. No @diffgazer/keys dependency required. Components import from local paths:

tsx
import { useNavigation } from "@/hooks/use-navigation"
import { useFocusTrap } from "@/hooks/use-focus-trap"
import { useFocusRestore } from "@/hooks/use-focus-restore"
import { useScrollLock } from "@/hooks/use-scroll-lock"

Option 4: Manual copy-paste

Copy the hook source code from the Hook Source Code section below into your project.


Quick start

Wrap your app with KeyboardProvider, then use hooks anywhere inside it:

tsx
import { KeyboardProvider, useKey } from "@diffgazer/keys"

function App() {
  return (
    <KeyboardProvider>
      <Editor />
    </KeyboardProvider>
  )
}

function Editor() {
  useKey("mod+s", (e) => {
    e.preventDefault()
    save()
  })

  useKey("Escape", () => close())

  return <div>...</div>
}

mod maps to Meta on Mac and Ctrl on Windows/Linux.


Hooks reference

HookPurposeProvider RequiredDocs
useNavigationDOM-driven list/tab navigation (arrow keys, Home/End, Enter)NoNavigation
useFocusTrapTrap Tab/Shift+Tab within a containerNoUtilities
useFocusRestoreCapture and restore focus around temporary UINoUtilities
useScrollLockPrevent scrolling (ref-counted)NoUtilities
useKeyBind keyboard shortcuts (single, array, or key map)Yes*useKey
useScopePush a named scope onto the stackYesuseScope
useScopedNavigationProvider-backed list navigation that participates in scopesYesNavigation
useFocusZoneMulti-zone keyboard navigation with transitionsYesFocus Zones
useActionRowNavigationTwo-zone row navigation for content plus inline actionsYesFocus Zones
keys()Map multiple hotkeys to the same handlerNoUtilities

* useKey silently does nothing without a provider — safe to use in portable components.

Standalone hooks (useNavigation, useFocusTrap, useFocusRestore, useScrollLock) work without KeyboardProvider and are available in copy mode. The rest require the full @diffgazer/keys package.


Patterns with @diffgazer/ui

@diffgazer/ui components declare their keyboard dependencies through registry metadata. In copy mode, dgadd can copy standalone hooks. In keys mode and runtime package mode, imports resolve through @diffgazer/keys. Components that expose controlled highlight state, selection state, or onKeyDown composition can still be coordinated by app-level handlers. See the Patterns page for complete integration examples with Menu, Dialog, CommandPalette, Tabs, and multi-zone layouts.


What each path gives you

Capabilitydgadd component installPackage installCLI copySource copy
useNavigation
useFocusTrap
useFocusRestore
useScrollLock
Component install support
KeyboardProvider
useKey (global shortcuts)
useScope (modal isolation)
useScopedNavigation (scope-aware list navigation)
useFocusZone (multi-panel)
useActionRowNavigation (row action navigation)
Zero external deps
Custom modifications

Hook source code

Loading hook source...

Utility hooks

Loading hook source...

For the complete API reference, visit the @diffgazer/keys documentation.