@usefy/use-key-press
v0.25.1
Published
A React hook for detecting keyboard key presses, shortcuts, and combinations
Maintainers
Readme
Overview
@usefy/use-key-press is a feature-rich React hook for detecting keyboard input — from a single key to complex modifier combinations. It supports cross-platform shortcuts, alternative bindings, physical-key matching, and callbacks with the raw event, making it ideal for command palettes, editor shortcuts, and game controls.
Part of the @usefy ecosystem — a collection of production-ready React hooks designed for modern applications.
Why use-key-press?
- Zero Dependencies — Pure React implementation with no external dependencies
- TypeScript First — Full type safety with comprehensive type definitions
- Shortcuts & Combinations —
"Escape","ctrl+s","mod+shift+k"out of the box - Alternative Bindings — Arrays are matched as OR:
["ctrl+s", "meta+s"] - Cross-Platform
mod— Resolves to Ctrl on Windows/Linux, Cmd on macOS - Key or Code Matching — Layout-aware (
event.key) or physical (event.code) for WASD-style controls - Callbacks —
onPress/onReleasereceive the raw event forpreventDefault - Robust — Ignores auto-repeat & typing in inputs (opt-in), resets on window blur
- SSR Compatible — Safe listener setup and automatic cleanup
Installation
# npm
npm install @usefy/use-key-press
# yarn
yarn add @usefy/use-key-press
# pnpm
pnpm add @usefy/use-key-pressPeer Dependencies
This package requires React 18 or 19:
{
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0"
}
}Quick Start
import { useKeyPress } from "@usefy/use-key-press";
function Modal({ onClose }: { onClose: () => void }) {
const escapePressed = useKeyPress("Escape");
useEffect(() => {
if (escapePressed) onClose();
}, [escapePressed, onClose]);
return <div>Press Escape to close</div>;
}Shortcut with a callback
// mod = Ctrl on Windows/Linux, Cmd on macOS
useKeyPress("mod+k", {
preventDefault: true,
onPress: () => openCommandPalette(),
});API Reference
useKeyPress(target, options?)
Detects whether target is currently pressed and returns a boolean.
Parameters
| Parameter | Type | Description |
| --------- | -------------------- | -------------------------------------------------- |
| target | KeyPressTarget | The key(s) or predicate to detect (see below) |
| options | UseKeyPressOptions | Optional configuration object |
target — KeyPressTarget
| Form | Example | Meaning |
| ------------------ | ------------------------------ | --------------------------------------------------------- |
| string | "Escape", "ctrl+s" | A single key or a +-joined modifier combination |
| string[] | ["ctrl+s", "meta+s"] | Alternative bindings — matches if any applies (OR) |
| predicate function | (e) => /^[0-9]$/.test(e.key) | Full control over matching |
Modifier aliases: ctrl/control, shift, alt/option/opt, meta/cmd/command/win, and mod (Ctrl on Windows/Linux, Cmd on macOS).
Key aliases: esc/escape, space, up/down/left/right (arrows), enter/return, del/delete, tab, backspace, plus, and more.
Options — UseKeyPressOptions
| Option | Type | Default | Description |
| --------------------- | --------------------------------------------- | ---------- | ----------------------------------------------------------------------------------------------- |
| target | Window \| Document \| HTMLElement \| RefObject \| null | window | The element to attach listeners to. null detaches |
| eventType | "keydown" \| "keyup" \| "both" | "both" | Which events drive the pressed state. "both" tracks the full held lifecycle |
| enabled | boolean | true | When false, no listeners are attached and the result is always false |
| preventDefault | boolean | false | Call event.preventDefault() on a match (e.g. override the browser's ctrl+s) |
| stopPropagation | boolean | false | Call event.stopPropagation() on a match |
| ignoreRepeat | boolean | true | Ignore auto-repeated keydown events for onPress (does not affect the returned boolean) |
| ignoreInputElements | boolean | false | Ignore events from <input>, <textarea>, <select>, and contenteditable elements |
| caseSensitive | boolean | false | Match letters case-sensitively (key mode only) |
| matchBy | "key" \| "code" | "key" | Match the logical key (event.key) or the physical key (event.code) |
| exactModifiers | boolean | true | Require an exact modifier match. When true, "ctrl+s" does not match ctrl+shift+s |
| onPress | (event: KeyboardEvent) => void | — | Called on a matching keydown, with the raw event |
| onRelease | (event: KeyboardEvent) => void | — | Called when a matched key is released |
Returns
boolean — true while the target key/combination is pressed (with the default eventType: "both").
Examples
Cross-platform save shortcut
import { useKeyPress } from "@usefy/use-key-press";
function Editor({ onSave }: { onSave: () => void }) {
// Array = OR: matches Ctrl+S on Windows/Linux and Cmd+S on macOS
useKeyPress(["ctrl+s", "meta+s"], {
preventDefault: true, // stop the browser's Save dialog
onPress: () => onSave(),
});
return <textarea />;
}Command palette
import { useState } from "react";
import { useKeyPress } from "@usefy/use-key-press";
function App() {
const [open, setOpen] = useState(false);
useKeyPress("mod+k", {
preventDefault: true,
onPress: () => setOpen((prev) => !prev),
});
return open ? <CommandPalette /> : null;
}WASD game controls (physical keys)
import { useKeyPress } from "@usefy/use-key-press";
function Game() {
// matchBy: "code" is layout-independent (works on AZERTY, Dvorak, etc.)
const up = useKeyPress("w", { matchBy: "code" });
const left = useKeyPress("a", { matchBy: "code" });
const down = useKeyPress("s", { matchBy: "code" });
const right = useKeyPress("d", { matchBy: "code" });
return <Player up={up} left={left} down={down} right={right} />;
}Ignore shortcuts while typing
import { useKeyPress } from "@usefy/use-key-press";
function App() {
// "f" opens a filter — but not while the user types "f" in a field
useKeyPress("f", {
ignoreInputElements: true,
onPress: () => openFilter(),
});
return <input placeholder="Type freely..." />;
}Custom predicate
import { useKeyPress } from "@usefy/use-key-press";
function NumericField() {
const digitPressed = useKeyPress((e) => /^[0-9]$/.test(e.key));
return <div data-active={digitPressed}>Press any number</div>;
}Scoped to a specific element
import { useRef } from "react";
import { useKeyPress } from "@usefy/use-key-press";
function ScopedInput() {
const ref = useRef<HTMLInputElement>(null);
// Only reacts while the input is the event target
const enterPressed = useKeyPress("Enter", { target: ref });
return <input ref={ref} data-submit={enterPressed} />;
}TypeScript
This hook is written in TypeScript and exports comprehensive type definitions.
import {
useKeyPress,
type KeyPressTarget,
type UseKeyPressOptions,
type KeyPressEventType,
type KeyPressMatchBy,
type ParsedShortcut,
} from "@usefy/use-key-press";
const options: UseKeyPressOptions = {
preventDefault: true,
matchBy: "key",
onPress: (event) => console.log(event.key),
};
const isPressed: boolean = useKeyPress("mod+shift+p", options);Utility helpers such as parseShortcut, isKeyPressSupported, and isApplePlatform are also exported for advanced use.
Behavior Notes
- Held state — With the default
eventType: "both", the returned boolean istrueonly while the key/combination is physically held. - Stuck-key prevention — If focus leaves the window while a key is held (so
keyupis never delivered), the state resets onblur. To keeponPress/onReleasebalanced,onReleasealso fires once on thatblur, receiving a synthetickeyupevent.onReleaseis not fired when the hook is disabled (enabled: false) or unmounted while pressed. - Modifier release — For combinations, releasing either the primary key or a modifier that was part of the held trigger ends the pressed state. Releasing an unrelated modifier (e.g.
Shiftwhile holding a bare"a") does not reset it. - SSR — In non-browser environments the hook safely returns
falseand attaches no listeners.
Browser Support
This hook uses standard keydown/keyup events, supported in all modern browsers:
- Chrome 1+
- Firefox 1+
- Safari 1+
- Edge 12+
- Opera 7+
For SSR environments, the hook gracefully degrades and returns false.
Testing
This package maintains comprehensive test coverage to ensure reliability and stability.
Test Coverage
📊 View Detailed Coverage Report (GitHub Pages)
Test Files
useKeyPress.test.ts— 60 tests for hook behavior and utilities
Total: 60 tests
License
MIT © mirunamu
This package is part of the usefy monorepo.
