ui/integrations/keys-patterns
Patterns
Complete integration examples for @diffgazer/keys with @diffgazer/ui components including Menu, Dialog, CommandPalette, Tabs, and multi-zone layouts.
@diffgazer/ui components include their local keyboard behavior. Use @diffgazer/keys when you need app-level shortcuts, scoped shortcut stacks, package-mode hook imports, or custom composites that are not already covered by a UI component.
Menu with controlled state
tsx
import { useState } from "react"
import { Menu, MenuItem } from "@/components/ui/menu"
function ControlledMenu() {
const [selectedId, setSelectedId] = useState<string | null>(null)
const [highlighted, setHighlighted] = useState<string | null>(null)
return (
<Menu
aria-label="File menu"
selectedId={selectedId}
highlighted={highlighted}
onSelect={setSelectedId}
onHighlightChange={setHighlighted}
>
<MenuItem id="new">New File</MenuItem>
<MenuItem id="open">Open File</MenuItem>
<MenuItem id="save">Save</MenuItem>
</Menu>
)
}Dialog with shortcut scope
tsx
import { useScope, useKey } from "@diffgazer/keys"
import {
Dialog, DialogContent, DialogHeader, DialogTitle,
DialogBody, DialogFooter, DialogClose, DialogAction,
} from "@/components/ui/dialog"
function ConfirmDialog({ open, onOpenChange, onConfirm }) {
const scope = useScope("confirm-dialog", { enabled: open })
useKey("mod+enter", onConfirm, { scope })
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>Confirm Action</DialogTitle>
</DialogHeader>
<DialogBody>Save changes?</DialogBody>
<DialogFooter>
<DialogClose bracket variant="ghost">Cancel</DialogClose>
<DialogAction onClick={onConfirm}>Save</DialogAction>
</DialogFooter>
</DialogContent>
</Dialog>
)
}Dialog owns focus trapping, Escape handling, and scroll locking. Use @diffgazer/keys here for app-level shortcuts that should be active only while the dialog scope is on top.
Command palette with useScope
tsx
import { useScope } from "@diffgazer/keys"
import {
CommandPalette, CommandPaletteContent, CommandPaletteInput,
CommandPaletteList, CommandPaletteGroup, CommandPaletteItem,
} from "@/components/ui/command-palette"
function AppPalette({ open, onOpenChange, onActivate }) {
useScope("palette", { enabled: open })
return (
<CommandPalette open={open} onOpenChange={onOpenChange} onActivate={onActivate}>
<CommandPaletteContent>
<CommandPaletteInput placeholder="Search..." />
<CommandPaletteList>
<CommandPaletteGroup heading="Files">
<CommandPaletteItem id="readme">README.md</CommandPaletteItem>
<CommandPaletteItem id="index">index.ts</CommandPaletteItem>
</CommandPaletteGroup>
</CommandPaletteList>
</CommandPaletteContent>
</CommandPalette>
)
}The scope stack ensures that while the palette is open, only palette-scoped handlers fire. Global shortcuts like mod+k are automatically suppressed until the palette closes.
Multi-zone layout with useFocusZone
tsx
import { useFocusZone, useKey } from "@diffgazer/keys"
function TwoPaneLayout() {
const { zone, isZone, getKeyOptions, getZoneProps } = useFocusZone({
initial: "sidebar",
zones: ["sidebar", "main"] as const,
tabCycle: ["sidebar", "main"] as const,
transitions: ({ zone, key }) => {
if (zone === "sidebar" && key === "ArrowRight") return "main"
if (zone === "main" && key === "ArrowLeft") return "sidebar"
return null
},
})
useKey("n", () => createNew(), getKeyOptions("sidebar"))
useKey("e", () => edit(), getKeyOptions("main"))
return (
<div className="flex">
<aside
{...getZoneProps("sidebar")}
className={isZone("sidebar") ? "ring-1 ring-foreground" : ""}
>
Sidebar
</aside>
<main
{...getZoneProps("main")}
className={isZone("main") ? "ring-1 ring-foreground" : ""}
>
Content
</main>
</div>
)
}Tabs with built-in navigation
tsx
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"
function KeyboardTabs() {
return (
<Tabs defaultValue="preview">
<TabsList>
<TabsTrigger value="preview">Preview</TabsTrigger>
<TabsTrigger value="code">Code</TabsTrigger>
<TabsTrigger value="api">API</TabsTrigger>
</TabsList>
<TabsContent value="preview">Preview content</TabsContent>
<TabsContent value="code">Code content</TabsContent>
<TabsContent value="api">API content</TabsContent>
</Tabs>
)
}Custom composite with useNavigation
tsx
import { useRef } from "react"
import { useNavigation } from "@diffgazer/keys"
const items = [
{ id: "main", label: "main" },
{ id: "develop", label: "develop" },
{ id: "feature-auth", label: "feature/auth" },
]
function getOptionId(id) {
return `branch-${id}`
}
function BranchList({ onChoose }) {
const containerRef = useRef<HTMLDivElement>(null)
const { highlighted, isHighlighted, onKeyDown } = useNavigation({
containerRef,
role: "option",
wrap: true,
onEnter: onChoose,
onSelect: onChoose,
})
return (
<div
ref={containerRef}
role="listbox"
aria-label="Branches"
aria-activedescendant={highlighted ? getOptionId(highlighted) : undefined}
tabIndex={0}
onKeyDown={onKeyDown}
>
{items.map((item) => (
<div
key={item.id}
id={getOptionId(item.id)}
data-value={item.id}
role="option"
aria-selected={isHighlighted(item.id)}
data-highlighted={isHighlighted(item.id) ? "" : undefined}
>
{item.label}
</div>
))}
</div>
)
}Combined layout example
tsx
import { useState } from "react"
import { useFocusZone, useKey, useScope } from "@diffgazer/keys"
import { Button } from "@/components/ui/button"
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"
import { Menu, MenuItem } from "@/components/ui/menu"
function ReviewWorkspace() {
const [confirmOpen, setConfirmOpen] = useState(false)
const scope = useScope("confirm", { enabled: confirmOpen })
const { isZone, getKeyOptions, getZoneProps } = useFocusZone({
initial: "queue",
zones: ["queue", "details"] as const,
transitions: ({ zone, key }) => {
if (zone === "queue" && key === "ArrowRight") return "details"
if (zone === "details" && key === "ArrowLeft") return "queue"
return null
},
})
useKey("mod+k", () => openCommandPalette(), { preventDefault: true })
useKey("Enter", () => setConfirmOpen(true), getKeyOptions("queue"))
useKey("Escape", () => setConfirmOpen(false), { scope })
return (
<div className="grid grid-cols-[16rem_1fr] gap-4">
<Menu
{...getZoneProps("queue")}
aria-label="Review queue"
className={isZone("queue") ? "ring-1 ring-foreground" : ""}
>
<MenuItem id="api">API issue</MenuItem>
<MenuItem id="tests">Missing test</MenuItem>
</Menu>
<section
{...getZoneProps("details")}
className={isZone("details") ? "ring-1 ring-foreground" : ""}
>
<Button onClick={() => setConfirmOpen(true)}>Apply fix</Button>
</section>
<Dialog open={confirmOpen} onOpenChange={setConfirmOpen}>
<DialogContent>
<DialogTitle>Apply fix?</DialogTitle>
<Button onClick={() => setConfirmOpen(false)}>Confirm</Button>
</DialogContent>
</Dialog>
</div>
)
}