GitHub
Open Github

UseKeyCursor

Declarative key cursor with dynamic positioning and auto-cleanup.

Open the modal then press Escape or Delete

Installation

pnpm add @hookraft/use-key-cursor

useKeyCursor

Animate a circle cursor to any element when the user presses a keyboard key. The cursor flies from a corner of the screen, lands on the target, squeezes to simulate a click, then fades out. Designed for modal close buttons, confirm dialogs, and any keyboard-driven UX that needs to feel alive.

Bindings are automatically silenced when the user is typing inside an input, textarea, select, or contenteditable element — so you never interfere with normal keyboard input.

import { useKeyCursor } from "@hookraft/use-key-cursor"
useKeyCursor({
  keys: {
    Escape: closeRef,
    Enter:  confirmRef,
  },
  origin: "top-right",
  color:  "#000000",
  theme:  "system",
})

Full Example

"use client"

import { useRef } from "react"
import { useKeyCursor } from "@hookraft/use-key-cursor"

function DeleteModal({ onClose, onConfirm }) {
  const closeRef   = useRef<HTMLButtonElement>(null)
  const confirmRef = useRef<HTMLButtonElement>(null)

  useKeyCursor({
    keys: {
      Escape: closeRef,
      Enter:  confirmRef,
    },
    origin: "top-right",
    color:  "#dc2626",
    theme:  "system",
    onTrigger: (key, el) => {
      console.log(`${key} triggered`, el.textContent)
    },
  })

  return (
    <div className="modal">
      <h2>Delete file?</h2>
      <p>This action cannot be undone.</p>
      <div className="actions">
        <button ref={closeRef}   onClick={onClose}>Cancel</button>
        <button ref={confirmRef} onClick={onConfirm}>Delete</button>
      </div>
    </div>
  )
}

You can also target elements using a CSS selector string instead of a ref:

useKeyCursor({
  keys: {
    Escape: "#cancel-btn",
    Enter:  "#confirm-btn",
    d:      deleteRef,
  },
})

Options

PropTypeDefault
keys
Record<string, useKeyCursor.KeyTarget>
-
origin
useKeyCursor.Origin
"top-right"
color
string
"#000000"
theme
useKeyCursor.Theme
"system"
onTrigger
(key: string, element: HTMLElement) => void
-
ignoreWhen
() => boolean
-

Examples

Origin

Change where the cursor spawns from. Useful for matching the position of a keyboard shortcut hint in your UI.

Color

The ring color is the only visual property you can customize. Use it to match your brand or signal intent — red for destructive actions, green for confirm, and so on.


Notes

The cursor element is appended directly to document.body and removed on unmount — no portals or extra wrappers needed z-index is 999999 so the cursor renders above modals and overlays Fires a real .click() on the target element so all existing onClick handlers work without any changes If the cursor is already animating, subsequent keypresses are ignored until the animation finishes Works in Next.js App Router — add "use client" to the component that calls the hook