browser-testing-with-s…
Use when testing web applications with visual verification - automates Chrome browser interactions, element selection, and…
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
Claude Code
$ npx -y skills add AgentWorkforce/relay --skill implementing-command-palettes --agent claude-code
Codex
$ npx skills add AgentWorkforce/relay --skill implementing-command-palettes -a codex -y
Cursor
$ npx skills add AgentWorkforce/relay --skill implementing-command-palettes -a cursor -y
Gemini CLI
$ npx skills add AgentWorkforce/relay --skill implementing-command-palettes -a gemini-cli -y
GitHub Copilot
$ npx skills add AgentWorkforce/relay --skill implementing-command-palettes -a github-copilot -y
OpenCode
$ npx skills add AgentWorkforce/relay --skill implementing-command-palettes -a opencode -y
Windsurf
$ npx skills add AgentWorkforce/relay --skill implementing-command-palettes -a windsurf -y
name: implementing-command-palettes description: 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
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.
| 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 |
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:**
<input
value={query}
onChange={(e) => {
setQuery(e.target.value);
setSelectedIndex(0); // Reset to first item when query changes
}}
/>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>
));
}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'`?**
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"`.
Command palettes often suffer from infinite re-renders when command objects are recreated every render.
// 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
}// GOOD: Icons defined OUTSIDE component
const refreshIcon = <RefreshCw size={16} />;
const refreshSpinIcon = <RefreshCw size={16} className="animate-spin" />;
const noopTired of copy/pasting from Claude Code into Slack so your coworker can paste it into their agent? Ever put an important rule in a skill or AGENTS.md, only for the agent to completely ignore it? You and your teammates agents ever build the same thing?
Use when testing web applications with visual verification - automates Chrome browser interactions, element selection, and…
Use when coordinating multiple AI agents with Agent Relay's workflow engine and need to pick the right orchestration pattern -…
Use when creating or improving Claude Code agents. Expert guidance on agent file structure, frontmatter, persona definition, tool…
Use when creating or publishing Claude Code hooks - covers executable format, event types, JSON I/O, exit codes, security…
Use when creating or fixing .claude/rules/ files - provides correct paths frontmatter (not globs), glob patterns, and avoids…
Use when creating new Claude Code skills or improving existing ones - ensures skills are discoverable, scannable, and effective…
Direct browser control via CDP for web interaction: automation, scraping, testing, screenshots, and site/app work. Also covers…
Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling…