# Desktop 8: keyboard shortcuts editor

> A shortcuts editor: commands grouped and searchable by name or by pressing the keys, a recorder on each row, a warning with Replace when keys are taken, reset per command, Restore defaults, and each platform's notation.

A shadcn/ui block from the [@ballmac](https://ui.ballmac.com/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/ballmac/desktop-8).

- Page, with a live preview: https://shadcn.exchange/r/ballmac/desktop-8
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/ballmac/desktop-8-light-301306af8910895b.png
- Registry item: https://ui.ballmac.com/r/desktop-8.json
- Categories: blocks, desktop
- Listed: 2026-10-18, source last changed 2026-10-18

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/ballmac/desktop-8.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `lucide-react`
- Registry items: `utils`, `https://ui.ballmac.com/r/app-window.json`, `https://ui.ballmac.com/r/button.json`, `https://ui.ballmac.com/r/feel-presets.json`, `https://ui.ballmac.com/r/shortcut.json`

## Files

### `registry/ballmac/components/blocks/desktop-8/desktop-8.tsx`

```tsx
// Ballmac UI: Desktop 8. https://ui.ballmac.com/blocks/desktop-8
"use client"

import * as React from "react"
import { Keyboard, RotateCcw, Search, TriangleAlert } from "lucide-react"

import { AppWindow, AppWindowContent, AppWindowTitleBar } from "@/components/ballmac/app-window"
import { Button } from "@/components/ballmac/button"
import { useFeel, type FeelId } from "@/lib/ballmac/feel"
import { formatShortcut, normalizeShortcut, shortcutFromEvent, usesMacKeys } from "@/lib/ballmac/shortcut"
import { cn } from "@/lib/utils"

type Desktop8Command = {
  id: string
  label: string
  /** The section it is listed under ("File", "Edit", "View"). */
  group: string
  /** The default shortcut, such as "Mod+Shift+N". Empty for none. */
  shortcut: string
}

type Desktop8Props = Omit<React.ComponentProps<"div">, "children" | "onChange"> & {
  /** Which platform the window handles like. Defaults to the page's feel. */
  feel?: FeelId
  /** Draw the window frame. Turn off inside a real Tauri or Electron window, or a settings pane. */
  frame?: boolean
  /** Every command that can have a shortcut, with its default. */
  commands?: Desktop8Command[]
  /** Shortcuts people changed, by command id ("" for removed). Pass with onValuesChange to control. */
  values?: Record<string, string>
  defaultValues?: Record<string, string>
  /** Called with every change, holding only the shortcuts that differ from the defaults. */
  onValuesChange?: (values: Record<string, string>) => void
  onClose?: () => void
}

const sample: Desktop8Command[] = [
  { id: "new", label: "New note", group: "File", shortcut: "Mod+N" },
  { id: "new-window", label: "New window", group: "File", shortcut: "Mod+Shift+N" },
  { id: "save", label: "Save", group: "File", shortcut: "Mod+S" },
  { id: "export", label: "Export as PDF", group: "File", shortcut: "" },
  { id: "find", label: "Find in note", group: "Edit", shortcut: "Mod+F" },
  { id: "find-all", label: "Find in all notes", group: "Edit", shortcut: "Mod+Shift+F" },
  { id: "duplicate", label: "Duplicate line", group: "Edit", shortcut: "Mod+Shift+D" },
  { id: "palette", label: "Command palette", group: "View", shortcut: "Mod+K" },
  { id: "sidebar", label: "Toggle sidebar", group: "View", shortcut: "Mod+Alt+S" },
  { id: "focus", label: "Focus mode", group: "View", shortcut: "Mod+Shift+Enter" },
]

/**
 * A keyboard shortcuts editor: every command grouped and searchable by name or by pressing the keys, a recorder on each row, a
 * warning with Replace when a combination is already taken, reset per command, Restore defaults, and each platform's notation.
 */
function Desktop8({
  feel: feelProp,
  frame = true,
  commands = sample,
  values: valuesProp,
  defaultValues = {},
  onValuesChange,
  onClose,
  className,
  ...props
}: Desktop8Props) {
  const page = useFeel().feel
  const feel = feelProp ?? page
  const mac = usesMacKeys(feel)
  const id = React.useId()
  const [local, setLocal] = React.useState(defaultValues)
  const overrides = valuesProp ?? local
  const [query, setQuery] = React.useState("")
  const [byKeys, setByKeys] = React.useState<string | null>(null)
  const [listening, setListening] = React.useState(false)
  const [recording, setRecording] = React.useState<string | null>(null)
  const [conflict, setConflict] = React.useState<{ id: string; shortcut: string; other: Desktop8Command } | null>(null)
  const [announce, setAnnounce] = React.useState("")

  const current = (c: Desktop8Command) => (c.id in overrides ? overrides[c.id]! : c.shortcut)
  const commit = (next: Record<string, string>) => {
    // keep only real changes, so the stored settings stay small and defaults can change later
    const clean = Object.fromEntries(Object.entries(next).filter(([k, v]) => commands.find((c) => c.id === k)?.shortcut !== v))
    if (!valuesProp) setLocal(clean)
    onValuesChange?.(clean)
  }
  const assign = (cmdId: string, shortcut: string) => commit({ ...overrides, [cmdId]: shortcut })

  function record(cmd: Desktop8Command, shortcut: string) {
    setRecording(null)
    const other = commands.find((c) => c.id !== cmd.id && current(c) && normalizeShortcut(current(c), feel) === normalizeShortcut(shortcut, feel))
    if (other) {
      setConflict({ id: cmd.id, shortcut, other })
      return
    }
    assign(cmd.id, shortcut)
    setAnnounce(`${cmd.label}: ${formatShortcut(shortcut, feel)}`)
  }

  const q = query.trim().toLowerCase()
  const visible = commands.filter((c) => (byKeys ? normalizeShortcut(current(c), feel) === normalizeShortcut(byKeys, feel) : !q || c.label.toLowerCase().includes(q) || c.group.toLowerCase().includes(q)))
  const groups = [...new Set(visible.map((c) => c.group))]
  const changed = Object.keys(overrides).length

  const body = (
    <div className="flex min-h-0 flex-1 flex-col">
      <div className="flex flex-wrap items-center gap-2 border-b p-3">
        <div className="bg-background focus-within:ring-ring/50 flex h-8 min-w-0 flex-1 items-center gap-2 rounded-md border px-2.5 focus-within:ring-[3px]">
          <Search aria-hidden="true" className="text-muted-foreground size-3.5" />
          {listening || byKeys ? (
            <button
              type="button"
              autoFocus
              aria-label="Search by keys"
              aria-describedby={`${id}-keys-hint`}
              onKeyDown={(e) => {
                if (e.key === "Tab") return
                e.preventDefault()
                if (e.key === "Escape") {
                  setListening(false)
                  setByKeys(null)
                  return
                }
                const s = shortcutFromEvent(e, mac, false)
                if (s) {
                  setByKeys(s)
                  setListening(false)
                }
              }}
              onClick={() => {
                setByKeys(null)
                setListening(true)
              }}
              className="text-muted-foreground min-w-0 flex-1 text-start text-[13px] outline-none"
            >
              {byKeys ? (
                <span dir="ltr" className="text-foreground font-mono">{formatShortcut(byKeys, feel)}</span>
              ) : (
                "Press keys…"
              )}
            </button>
          ) : (
            <input type="search" aria-label="Search commands" placeholder="Search commands" value={query} onChange={(e) => setQuery(e.target.value)} className="placeholder:text-muted-foreground min-w-0 flex-1 bg-transparent text-[13px] outline-none" />
          )}
          <span id={`${id}-keys-hint`} className="sr-only">
            Press a key combination to find the command that uses it. Escape goes back to searching by name.
          </span>
        </div>
        <Button
          variant={listening || byKeys ? "secondary" : "outline"}
          size="sm"
          aria-pressed={listening || !!byKeys}
          onClick={() => {
            if (listening || byKeys) {
              setListening(false)
              setByKeys(null)
            } else {
              setQuery("")
              setListening(true)
            }
          }}
        >
          <Keyboard aria-hidden="true" />
          By keys
        </Button>
      </div>

      {conflict ? (
        <div role="alert" className="bg-chart-3/10 border-chart-3/30 flex flex-wrap items-center gap-3 border-b px-4 py-2.5 text-[13px]">
          <TriangleAlert aria-hidden="true" className="text-chart-3 size-4 shrink-0" />
          <p className="min-w-0 flex-1 text-pretty">
            <span dir="ltr" className="font-mono font-medium">{formatShortcut(conflict.shortcut, feel)}</span> is already used by “{conflict.other.label}”.
          </p>
          <div className="flex gap-2">
            <Button variant="outline" size="sm" onClick={() => setConflict(null)}>
              Cancel
            </Button>
            <Button
              size="sm"
              onClick={() => {
                commit({ ...overrides, [conflict.id]: conflict.shortcut, [conflict.other.id]: "" })
                setAnnounce(`Replaced. ${conflict.other.label} has no shortcut now.`)
                setConflict(null)
              }}
            >
              Replace
            </Button>
          </div>
        </div>
      ) : null}

      <div className="min-h-0 flex-1 overflow-y-auto p-3">
        {groups.map((g) => (
          <section key={g} aria-labelledby={`${id}-${g}`} className="mb-4 last:mb-0">
            <h3 id={`${id}-${g}`} className="text-muted-foreground px-2 pb-1 text-xs font-medium">
              {g}
            </h3>
            <ul className="divide-border bg-card divide-y overflow-hidden rounded-lg border">
              {visible
                .filter((c) => c.group === g)
                .map((c) => {
                  const value = current(c)
                  const isRec = recording === c.id
                  const custom = c.id in overrides
                  return (
                    <li key={c.id} className="flex items-center gap-2 px-3 py-1.5">
                      <span className="min-w-0 flex-1 truncate text-[13px]">
                        <span id={`${id}-${c.id}`}>{c.label}</span>
                        {custom ? <span className="text-muted-foreground ms-2 text-xs">Changed</span> : null}
                      </span>
                      <button
                        type="button"
                        aria-labelledby={`${id}-${c.id}`}
                        aria-describedby={`${id}-rec-hint`}
                        aria-pressed={isRec}
                        onClick={() => setRecording(isRec ? null : c.id)}
                        onBlur={() => isRec && setRecording(null)}
                        onKeyDown={(e) => {
                          if (!isRec) return
                          if (e.key === "Tab") return setRecording(null)
                          e.preventDefault()
                          if (e.key === "Escape") return setRecording(null)
                          if (e.key === "Backspace" || e.key === "Delete") {
                            setRecording(null)
                            assign(c.id, "")
                            setAnnounce(`${c.label}: no shortcut`)
                            return
                          }
                          const s = shortcutFromEvent(e, mac, false)
                          if (s) record(c, s)
                        }}
                        className={cn(
                          "border-input bg-background inline-flex h-7 min-w-24 items-center justify-center rounded-md border px-2 font-mono text-xs tabular-nums outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50",
                          isRec && "border-ring ring-ring/30 text-muted-foreground ring-[3px]",
                          !value && !isRec && "text-muted-foreground"
                        )}
                      >
                        <span dir="ltr">{isRec ? "Type keys…" : value ? formatShortcut(value, feel) : "None"}</span>
                      </button>
                      <Button
                        variant="ghost"
                        size="icon-sm"
                        aria-label={`Reset ${c.label}`}
                        className={cn("size-7", !custom && "invisible")}
                        tabIndex={custom ? undefined : -1}
                        aria-hidden={custom ? undefined : true}
                        onClick={() => {
                          const { [c.id]: _, ...rest } = overrides
                          commit(rest)
                          setAnnounce(`${c.label} reset to ${c.shortcut ? formatShortcut(c.shortcut, feel) : "no shortcut"}`)
                        }}
                      >
                        <RotateCcw aria-hidden="true" />
                      </Button>
                    </li>
                  )
                })}
            </ul>
          </section>
        ))}
        {!visible.length ? (
          <p className="text-muted-foreground py-10 text-center text-sm">
            {byKeys ? (
              <>
                No command uses <span dir="ltr" className="font-mono">{formatShortcut(byKeys, feel)}</span>.
              </>
            ) : (
              <>No commands match “{query}”.</>
            )}
          </p>
        ) : null}
        <span id={`${id}-rec-hint`} className="sr-only">
          Press to record. Then type the new keys; Escape cancels, Delete removes the shortcut.
        </span>
      </div>

      <div className="flex items-center gap-3 border-t px-4 py-2.5">
        <p className="text-muted-foreground flex-1 text-xs">{changed ? (changed === 1 ? "1 shortcut changed" : `${changed} shortcuts changed`) : "All shortcuts are the defaults"}</p>
        <Button variant="outline" size="sm" disabled={!changed} onClick={() => { commit({}); setAnnounce("Every shortcut restored") }}>
          {mac ? "Restore Defaults" : "Restore defaults"}
        </Button>
      </div>
      <p aria-live="polite" className="sr-only">
        {announce}
      </p>
    </div>
  )

  if (!frame)
    return (
      <div data-slot="desktop-8" data-feel={feelProp} className={cn("bg-card flex min-h-0 flex-col overflow-hidden rounded-xl border", className)} {...props}>
        {body}
      </div>
    )
  return (
    <AppWindow data-slot="desktop-8" feel={feel} onClose={onClose} className={cn("h-[32rem] w-full max-w-xl", className)} {...props}>
      <AppWindowTitleBar title={mac ? "Keyboard Shortcuts" : "Keyboard shortcuts"} icon={<Keyboard />} />
      <AppWindowContent className="flex flex-col overflow-hidden">{body}</AppWindowContent>
    </AppWindow>
  )
}

export { Desktop8, type Desktop8Props, type Desktop8Command }
```
