Keybindings

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:

PropertyTypeDescription
keystringThe key combination, written in react-hotkeys-hook syntax (e.g. "mod+z").
labelstringThe label shown for non-Mac platforms (e.g. "Ctrl Z").
labelMacstringThe label shown on Mac (e.g. "⌘ Z").
descriptionstringHuman-readable description shown in the help dialog.
groupstringGroup 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:

src/keybindings.ts
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:

src/App.tsx
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:

src/designer.d.ts
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:

src/components/keyboard-shortcuts.tsx
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