A React hook for creating keyboard shortcuts. Useful for improving accessibility and power-user workflows.
function useKeyboard<T extends HTMLElement>(
shortcuts: Array<KeyboardShortcut<T>>
): (root: T) => void
| Property | Type | Default | Description |
|---|---|---|---|
| key | string | Required | The key to listen for (case-insensitive) |
| metaKey | boolean | - | Require Cmd/Meta key (macOS) |
| ctrlKey | boolean | - | Require Ctrl key |
| action | (root: T) => void | Required | Function to execute when shortcut triggers |
| onlyWhenFocused | boolean | - | Only trigger when the element is focused |
import { useKeyboard } from "aidos-ui";
function SearchInput() {
const [query, setQuery] = useState("");
const ref = useKeyboard([
{
key: "Escape",
onlyWhenFocused: true,
action: () => setQuery(""),
},
]);
return (
<input
ref={ref}
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
/>
);
}
Create app-wide keyboard shortcuts by attaching to a root element:
function App() {
const searchRef = useRef(null);
const rootRef = useKeyboard([
{
key: "K",
metaKey: true,
action: () => searchRef.current?.focus(),
},
{
key: "/",
action: () => searchRef.current?.focus(),
},
]);
return (
<div ref={rootRef}>
<SearchInput ref={searchRef} />
{/* Rest of app */}
</div>
);
}
Combine with onlyWhenFocused to create context-aware shortcuts:
function CommandPalette() {
const [open, setOpen] = useState(false);
const [query, setQuery] = useState("");
const ref = useKeyboard([
{
key: "K",
metaKey: true,
action: () => setOpen(true),
},
{
key: "Escape",
onlyWhenFocused: true,
action: () => {
if (query) {
setQuery("");
} else {
setOpen(false);
}
},
},
]);
return (
<div ref={ref}>
{open && (
<Dialog>
<TextInput
value={query}
onValueChange={setQuery}
placeholder="Type a command..."
/>
</Dialog>
)}
</div>
);
}
Use metaKey and ctrlKey for modifier-based shortcuts:
const ref = useKeyboard([
// Cmd+S (macOS) / Ctrl+S (Windows/Linux)
{
key: "S",
metaKey: true,
action: () => saveDocument(),
},
// Ctrl+Enter
{
key: "Enter",
ctrlKey: true,
action: () => submitForm(),
},
// Cmd+Shift doesn't have a dedicated prop, but you can handle it:
{
key: "Z",
metaKey: true,
action: (root) => {
// Check for Shift in the action if needed
if (window.event?.shiftKey) {
redo();
} else {
undo();
}
},
},
]);
Register multiple shortcuts at once:
const ref = useKeyboard([
{ key: "ArrowUp", action: () => selectPrevious() },
{ key: "ArrowDown", action: () => selectNext() },
{ key: "Enter", action: () => confirm() },
{ key: "Escape", action: () => cancel() },
{ key: "Delete", action: () => remove() },
{ key: "Backspace", action: () => remove() },
]);
The hook is generic and preserves the element type:
// HTMLInputElement
const inputRef = useKeyboard<HTMLInputElement>([
{
key: "Escape",
onlyWhenFocused: true,
action: (input) => {
input.value = ""; // TypeScript knows this is HTMLInputElement
input.blur();
},
},
]);
// HTMLDivElement
const divRef = useKeyboard<HTMLDivElement>([
{
key: "Space",
action: (div) => div.click(),
},
]);
const ref = useKeyboard([
{ key: "B", metaKey: true, action: () => toggleBold() },
{ key: "I", metaKey: true, action: () => toggleItalic() },
{ key: "U", metaKey: true, action: () => toggleUnderline() },
{ key: "Z", metaKey: true, action: () => undo() },
]);
const ref = useKeyboard([
{ key: "H", action: () => navigate("/") },
{ key: "S", action: () => navigate("/settings") },
{ key: "?", action: () => showHelp() },
]);
const ref = useKeyboard([
{ key: "J", action: () => selectNext() },
{ key: "K", action: () => selectPrevious() },
{ key: "G", action: () => selectFirst() },
{ key: "G", ctrlKey: true, action: () => selectLast() },
]);