Listen to built-in shortcuts, register custom ones, and list all keybindings in your UI.
The designer ships with a set of built-in keybindings for common actions like undo, redo, duplicate, and zoom. You can listen to those shortcuts, override them, and add your own — all with full type safety.
Default Keybindings
The DEFAULT_KEYBINDINGS constant exposes the built-in shortcuts. Every keybinding follows the same shape:
| Property | Type | Description |
|---|---|---|
key | string | The key combination, written in react-hotkeys-hook syntax (e.g. "mod+z"). |
label | string | The label shown for non-Mac platforms (e.g. "Ctrl Z"). |
labelMac | string | The label shown on Mac (e.g. "⌘ Z"). |
description | string | Human-readable description shown in the help dialog. |
group | string | Group name used when listing shortcuts (e.g. "History"). |
Built-in groups include History, Layer, and Zoom. Each registered layer type also adds an ADD_LAYER_<NAME> keybinding when its keybinding field is set.
Listening to a Shortcut
Use useShortcut to run a callback when a keybinding fires. The hook accepts a KeybindingName — a typed union of all built-in names plus any custom or ADD_LAYER_* names you've registered:
import { useShortcut } from "@shadcn/designer";
function ShortcutHandler() {
useShortcut("DUPLICATE_LAYER", () => {
console.log("Duplicate pressed");
});
useShortcut("DELETE_LAYER", () => {
console.log("Delete pressed");
});
return null;
}If you mistype the name, TypeScript reports it instead of silently failing at runtime.
Adding Custom Keybindings
You can extend or override the defaults in three steps:
1. Define your keybindings
Define your app's keybindings in a single file. Use satisfies Record<string, Keybinding> to keep the literal keys narrow:
import { DEFAULT_KEYBINDINGS, type Keybinding } from "@shadcn/designer";
export const keybindings = {
...DEFAULT_KEYBINDINGS,
TOGGLE_PANEL_LEFT: {
key: "mod+b",
label: "Ctrl B",
labelMac: "⌘ B",
description: "Toggle left panel",
group: "Panels",
},
TOGGLE_PANEL_RIGHT: {
key: "mod+/",
label: "Ctrl /",
labelMac: "⌘ /",
description: "Toggle right panel",
group: "Panels",
},
} satisfies Record<string, Keybinding>;2. Pass them to the Designer
Pass your keybindings to <Designer /> so the runtime store knows about them:
import { Designer } from "@shadcn/designer";
import { keybindings } from "./keybindings";
function MyDesigner() {
return (
<Designer keybindings={keybindings}>
{/* ... */}
</Designer>
);
}3. Register them with TypeScript
Augment the DesignerKeybindings interface with InferKeybindings so useShortcut and KeybindingName know about your custom names:
import type { InferKeybindings } from "@shadcn/designer";
import type { keybindings } from "./keybindings";
declare module "@shadcn/designer" {
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
interface DesignerKeybindings extends InferKeybindings<typeof keybindings> {}
}See Type Safety for more on the registry pattern.
After registration, your custom names autocomplete and type-check:
useShortcut("TOGGLE_PANEL_LEFT", () => {
setLeftPanelOpen((open) => !open);
});Listing All Keybindings
Use useKeybindings to get the merged map of built-in, custom, and ADD_LAYER_* keybindings. This is useful for building a help dialog:
import { type Keybinding, useKeybindings } from "@shadcn/designer";
import { useIsMac } from "@shadcn/designer/hooks";
import { useMemo } from "react";
export function KeyboardShortcuts() {
const keybindings = useKeybindings();
const isMac = useIsMac();
const groups = useMemo(() => {
return Object.values(keybindings).reduce(
(acc, keybinding) => {
acc[keybinding.group] = [...(acc[keybinding.group] ?? []), keybinding];
return acc;
},
{} as Record<string, Keybinding[]>
);
}, [keybindings]);
return (
<div className="grid gap-6">
{Object.entries(groups).map(([group, items]) => (
<div key={group}>
<h3 className="text-muted-foreground">{group}</h3>
<ul className="grid gap-2">
{items.map((keybinding) => (
<li
key={keybinding.key}
className="flex items-center justify-between"
>
<span>{keybinding.description}</span>
<span>{isMac ? keybinding.labelMac : keybinding.label}</span>
</li>
))}
</ul>
</div>
))}
</div>
);
}Group rows by keybinding.group to render sections, then pick the platform-appropriate label using useIsMac.
Layer Keybindings
Layer types can declare their own keybinding alongside the layer definition. The designer registers it as ADD_LAYER_<TYPE> and wires it up to add a new layer when pressed:
createLayerType({
type: "shape",
name: "Shape",
keybinding: {
key: "s",
label: "S",
labelMac: "S",
description: "Add shape",
group: "New Layer",
},
// ...
});After registering the layer type, useShortcut("ADD_LAYER_SHAPE", ...) becomes a valid call and the entry appears in the merged map returned by useKeybindings.
Next Steps
- See Type Safety for more on the module augmentation pattern.
- Browse the hooks reference for
useShortcutanduseKeybindings.