implementing-command-palettes
Skill1.1k starsChanged 51 days agoApache-2.0View on GitHub
Use when building Cmd+K command palettes in React - covers keyboard navigation with arrow keys, keeping selected items in view with scrollIntoView, filtering with shortcut matching, and preventing infinite re-renders from reference instability
1 other project uses a version of this, 1 of them edited.
Adopted by
Steady: 0 new projects took it in the last 4 weeks, none the 4 weeks before.thin data
Most recently taken up by
- nek1987/auto-agent-harnessedited, first seen Oct 11, 2026
Who's talking about AgentWorkforce/relay
- AgentWorkforce/relaycast“fix(sdk): preserve rich blocks in realtime message events”GitHub · Nov 8, 2026 · 2 reactions
- AgentWorkforce/agentrelay.com“Add QR card redirect for AI Engineer World Fair event”GitHub · Jul 2, 2026 · 2 reactions
- A Hacker News story“Show HN: Simple SDK for agent-to-agent communication”Hacker News · Apr 28, 2026 · 2 points
- JoeElder21/Joeyyy“deps(relay): bump agent-relay from 11.2.0 to 11.11.0 in /connectors/relay in the relay group across 1 …”GitHub · Aug 10, 2026 · 1 reactions
- AgentWorkforce/relayfile“feat: add relayfile control-plane client”GitHub · Jun 29, 2026 · 1 reactions
The biggest 5 of 21 mentions linking to the repository that our crawl found. Each line is their own headline.
See all 21
Talked about on GitHub and Hacker News (21 times).
On GitHub · 20
- Nov 8, 2026fix(sdk): preserve rich blocks in realtime message events — AgentWorkforce/relaycast2 reactions · 3 comments
- Jul 2, 2026Add QR card redirect for AI Engineer World Fair event — AgentWorkforce/agentrelay.com2 reactions · 2 comments
- Aug 10, 2026deps(relay): bump agent-relay from 11.2.0 to 11.11.0 in /connectors/relay in the relay group across 1 directory — JoeElder21/Joeyyy1 reaction
- Jun 29, 2026feat: add relayfile control-plane client — AgentWorkforce/relayfile1 reaction · 4 comments
- Nov 3, 2026Ledger: task outcomes and expected evidence, not only done — solutionscay/skiff0 reactions
15 more on GitHub
- Nov 3, 2026Ledger: delivery records and receipts for sends and hand-offs — solutionscay/skiff0 reactions
- Oct 27, 2026Talk: Agent Led Development — milstan/sisw0 reactions · 1 comment
- Sep 28, 2026[Candidate] agentworkforce/relay/plugins/codex-relay-skill - agent-relay — ProSkillsMD/proskills0 reactions · 2 comments
- Sep 28, 2026[Candidate] agentworkforce/relay/.openskills/writing-relayflows - writing-relayflows — ProSkillsMD/proskills0 reactions · 1 comment
- Sep 27, 2026[Candidate] agentworkforce/relay/.agents/skills/review-fix-signoff-loop - review-fix-signoff-loop — ProSkillsMD/proskills0 reactions · 2 comments
- Sep 27, 2026[Candidate] agentworkforce/relay/.agents/skills/review-animations - review-animations — ProSkillsMD/proskills0 reactions · 2 comments
- Sep 27, 2026[Candidate] agentworkforce/relay/.agents/skills/relay-80-100-workflow - relay-80-100-workflow — ProSkillsMD/proskills0 reactions · 1 comment
- Sep 26, 2026[Candidate] agentworkforce/relay/.agents/skills/emil-design-eng - emil-design-eng — ProSkillsMD/proskills0 reactions · 2 comments
- Sep 26, 2026[Candidate] agentworkforce/relay/.agents/skills/creating-agent-skills-skill - creating-agent-skills — ProSkillsMD/proskills0 reactions · 2 comments
- Sep 26, 2026[Candidate] agentworkforce/relay/.agents/skills/choosing-swarm-patterns - choosing-swarm-patterns — ProSkillsMD/proskills0 reactions · 1 comment
- Sep 14, 2026Relay agent transport returns spawn metadata before task completion — AgentWorkforce/flows0 reactions
- Sep 11, 2026mount daemon can miss notify-flush SIGUSR1 acknowledgement in clean RelayFlow sandbox — AgentWorkforce/relayfile0 reactions
- Sep 10, 2026mount --once can exhaust its timeout on a 429 WebSocket dial before cursor resolution — AgentWorkforce/relayfile0 reactions
- Sep 6, 2026Rust SDK erases repeated 5xx registration diagnostics as Max retries exceeded — AgentWorkforce/relaycast0 reactions · 2 comments
- Aug 15, 2026factory dispatch exits 0 on hard failure — refusals are indistinguishable from success — AgentWorkforce/software-garden0 reactions
On Hacker News · 1
- Apr 28, 2026Show HN: Simple SDK for agent-to-agent communication2 points
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 1
- nek1987/auto-agent-harness★ 472% same
Wrote their own version · 0
None found.
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository27 other files it gives its agents.
- AGENTS.mdAGENTS.md
- choosing-swarm-patternsSkill
- creating-agent-skillsSkill
- emil-design-engSkill
- orchestrating-agent-relaySkill
- relay-80-100-workflowSkill
- review-animationsSkill
- review-fix-signoff-loopSkill
- setting-up-relayfileSkill
- using-agent-relaySkill
- writing-relayflowsSkill
- browser-testing-with-screenshotsSkill
- choosing-swarm-patternsSkill
- creating-claude-agentsSkill
- creating-claude-hooksSkill
- creating-claude-rulesSkill
- creating-skillsSkill
- debugging-websocket-issuesSkill
- deploying-to-staging-environmentSkill
- frontend-designSkill
- github-oauth-nango-integrationSkill
- orchestrating-agent-relaySkill
- prpm-json-best-practicesSkill
- relay-80-100-workflowSkill
- review-fix-signoff-loopSkill
- setting-up-relayfileSkill
- using-agent-relaySkill
The file
We split this skill into the rules it gives an agent
1 rule. About 0 are common, 1 is distinctive, and 0 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
0
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 235
Instead of comparing object references, compare by labels:
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Implementing Command Palettes
Overview
Command palettes (Cmd+K / Ctrl+K) need precise keyboard navigation, scroll behavior, and stable references to avoid re-render loops. This skill covers the mechanical patterns that make command palettes feel responsive.
When to Use
- Building a Cmd+K command palette in React
- Implementing arrow key navigation with visual selection
- Keeping selected items visible during keyboard navigation
- Filtering commands by label text AND keyboard shortcuts
- Experiencing infinite re-renders when commands update
Quick Reference
| Feature | Implementation |
| Arrow navigation | Track selectedIndex, clamp with Math.min/max |
| Keep in view | scrollIntoView({ block: 'nearest', behavior: 'smooth' }) |
| Shortcut matching | Strip spaces from shortcuts, match against query |
| Stable icons | Define icon elements outside component |
| Stable handlers | useCallback + noop constant for disabled states |
Keyboard Navigation
Index Management
const [selectedIndex, setSelectedIndex] = useState(0);
useEffect(() => {
if (!isOpen) return;
const handleKeyDown = (e: KeyboardEvent) => {
switch (e.key) {
case 'ArrowDown':
e.preventDefault();
// Clamp to last item
setSelectedIndex((prev) => Math.min(prev + 1, filteredItems.length - 1));
break;
case 'ArrowUp':
e.preventDefault();
// Clamp to first item
setSelectedIndex((prev) => Math.max(prev - 1, 0));
break;
case 'Enter':
e.preventDefault();
if (filteredItems[selectedIndex]) {
executeCommand(filteredItems[selectedIndex]);
close();
}
break;
case 'Escape':
e.preventDefault();
close();
break;
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [isOpen, filteredItems, selectedIndex, close]);Key patterns:
e.preventDefault()stops arrow keys from scrolling the pageMath.min/maxprevents index going out of bounds- Effect depends on
filteredItemsso navigation updates when filter changes
Reset Selection on Query Change
<input
value={query}
onChange={(e) => {
setQuery(e.target.value);
setSelectedIndex(0); // Reset to first item when query changes
}}
/>Keeping Selected Item in View
Using Refs Array
const itemRefs = useRef<(HTMLButtonElement | null)[]>([]);
// Scroll effect - runs when selection changes
useEffect(() => {
const selectedItem = itemRefs.current[selectedIndex];
if (selectedItem) {
selectedItem.scrollIntoView({
block: 'nearest', // Minimal scroll - only scroll if needed
behavior: 'smooth', // Smooth animation
});
}
}, [selectedIndex]);
// Assign refs in render
{
filteredItems.map((item, index) => (
<button
key={index}
ref={(el) => {
itemRefs.current[index] = el;
}}
className={index === selectedIndex ? 'bg-blue-100' : ''}
>
{item.label}
</button>
));
}Alternative: Single Ref for Selected Item
const selectedItemRef = useRef<HTMLButtonElement>(null);
useEffect(() => {
if (isOpen && selectedItemRef.current) {
selectedItemRef.current.scrollIntoView({
block: 'nearest',
behavior: 'smooth',
});
}
}, [isOpen, selectedIndex]);
// Only assign ref to selected item
<button
ref={index === selectedIndex ? selectedItemRef : null}
>Why block: 'nearest'?
'nearest'only scrolls if the element is outside the visible area'center'would scroll even when item is already visible, causing jarring movement'start'or'end'would always align to top/bottom
Filtering with Shortcut Matching
const filteredCommands = commands.filter((command) => {
const q = query.toLowerCase().trim();
if (!q) return true;
// Standard label matching
if (command.label.toLowerCase().includes(q)) return true;
// Shortcut matching: "gd" matches "g d", "gb" matches "g b"
if (command.shortcut) {
const shortcutNoSpaces = command.shortcut.toLowerCase().replace(/\s+/g, '');
if (shortcutNoSpaces.startsWith(q) || shortcutNoSpaces.includes(q)) {
return true;
}
}
// For numbered items (PRs, issues), match by number
if (command.type === 'pr') {
const numberMatch = q.match(/^#?(\d+)$/);
if (numberMatch) {
return String(command.pr.number).startsWith(numberMatch[1]);
}
}
return false;
});Why strip spaces from shortcuts?
Users type continuously without spaces. Shortcut "g d" should match when user types "gd".
Preventing Re-Render Loops
Command palettes often suffer from infinite re-renders when command objects are recreated every render.
Problem: Unstable References
// BAD: Icons recreated every render
function usePageCommands() {
const commands = useMemo(
() => [
{
label: 'Sync',
icon: <RefreshCw size={16} />, // New element every render!
action: () => onSync(), // New function every render!
},
],
[onSync]
); // Even with deps, icon is new
useRegisterCommands(commands); // Triggers re-registration → re-render loop
}Solution: Stable References
// GOOD: Icons defined OUTSIDE component
const refreshIcon = <RefreshCw size={16} />;
const refreshSpinIcon = <RefreshCw size={16} className="animate-spin" />;
const noop = () => {};
function usePageCommands({ onSync, isSyncing }: Props) {
// Memoize handlers
const handleSync = useCallback(() => onSync?.(), [onSync]);
const commands = useMemo(
() => [
{
label: isSyncing ? 'Syncing...' : 'Sync',
icon: isSyncing ? refreshSpinIcon : refreshIcon, // Stable references
action: isSyncing ? noop : handleSync, // noop, not undefined
},
],
[isSyncing, handleSync]
);
useRegisterCommands(commands);
}Label-Based Change Detection
Instead of comparing object references, compare by labels:
export function useRegisterCommands(commands: CommandItem[]) {
const { registerCommands, unregisterCommands } = useCommandPalette();
// Create stable ID based on LABELS, not object references
const commandIds = useMemo(
() =>
commands
.map((c) => {
if (c.type === 'nav') return `nav:${c.path}`;
return `action:${c.label}`;
})
.sort()
.join('|'),
[commands]
);
const commandsRef = useRef<CommandItem[]>(commands);
useEffect(() => {
commandsRef.current = commands;
});
const prevIdsRef = useRef<string>('');
useEffect(() => {
// Only register if structure actually changed
if (commandIds !== prevIdsRef.current) {
registerCommands(commandsRef.current);
prevIdsRef.current = commandIds;
return () => unregisterCommands(commandsRef.current);
}
}, [commandIds, registerCommands, unregisterCommands]);
}Command Type Patterns
type CommandItem =
| { type: 'action'; label: string; icon?: React.ReactNode; action: () => void; shortcut?: string }
| { type: 'nav'; label: string; icon?: React.ReactNode; path: string; shortcut?: string }
| { type: 'file'; file: FileType; label: string; icon?: React.ReactNode }
| { type: 'pr'; pr: PRType; label: string; icon?: React.ReactNode };
// Execute based on type
function executeCommand(command: CommandItem) {
switch (command.type) {
case 'action':
command.action();
break;
case 'nav':
navigate(command.path);
break;
case 'file':
onFileSelect(command.file);
break;
case 'pr':
navigate(`/repos/${command.owner}/${command.repo}/pulls/${command.pr.number}`);
break;
}
}Common Mistakes
| Mistake | Why It Fails | Fix |
| Icons inside useMemo | New icon element every render | Define icons as constants outside component |
| Not resetting index on filter | Arrow keys start from wrong position | setSelectedIndex(0) in onChange |
block: 'center' in scrollIntoView | Jarring scroll when item already visible | Use block: 'nearest' |
Missing e.preventDefault() | Arrow keys scroll page AND move selection | Add preventDefault for ArrowUp/Down |
| Forgetting cleanup in useEffect | Event listeners accumulate | Return cleanup function |
undefined for disabled action | Type error or click does nothing | Use noop constant |
Testing Checklist
- Cmd+K opens palette, Escape closes
- Arrow Down moves to next item (stops at last)
- Arrow Up moves to previous item (stops at first)
- Enter executes selected command and closes palette
- Selected item scrolls into view when navigating long lists
- Typing resets selection to first matching item
- Shortcuts like "gd" match commands with shortcut "g d"
- No console errors about re-renders or maximum update depth
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

