Designing a command palette with Cursor and AI
September 29, 2025
One of my current projects involves designing a command palette search experience. It has all of the standard features that most command palettes have, but we’re expanding it to support more advanced search capabilities, which introduces new states and delays to account for.
I knew that it would be hard to design this experience with Figma alone. Figma might be useful for the visual design and basic prototyping aspects of design, but command palettes have so many small design details that make them work, and a prototype made of static screens noodled together can’t totally replicate that behavior. I needed a way to get a feel for things like timing, LLM processing times, loading states, failures, fuzzy search settings, accessibility, and keyboard handling.
It was clear that working in code would give me the best approximation of how the command palette might work in production, so I jumped into Cursor and started building.
Working out the details in Cursor
I love Cursor. AI-integrated IDEs have been great for someone like me who has always been a designer first and a programmer second. Throughout my career I’ve invested a lot of time into keeping my programming skills up-to-date, but I still get stuck sometimes when I need to implement more complicated features. Cursor helps me get unstuck and work faster, which has allowed me to make building in code a more reliable part of my design process.
Because I know my way around React apps, I prefer to build prototypes like this using Cursor’s tab completion feature, as opposed to taking a pure vibe coding approach. Working with tab completions gives me complete more control over the output.
Simulating realistic search behavior
What I really wanted was to understand how it felt to use our hybrid command palette, but I didn’t want to have to recreate all of the technology that would power it. I ended up building a search framework that I could use to tweak different simulated settings that could be used across multiple prototypes.
Remember that I was concerned with mimicking realist search behavior in the browser, and not building a production-ready search system. The system I stood up included:
Mock API routes to mimic realistic network conditions
- Keyword search completes in
<150msto simulate local index lookup - Semantic search completes in
500ms–2.5sto simulate AI processing with a degree of randomness
// Fast keyword search - responds in ~100ms
export const runtime = "edge";
await new Promise((resolve) => setTimeout(resolve, 100));
// Semantic search - simulates AI processing with 2.5s delay
export const runtime = "nodejs";
await new Promise((resolve) => setTimeout(resolve, 2500));Robust mock search results with realistic and diverse metadata
I used AI to generate a robust set of default search results and store them in a mock data object. I continued to improve the mock data (made it more realistic) throughout the design process and it became a consistent source of truth that fed several prototypes.
State management
The search component uses a state machine to prevent race conditions and ensure predictable behavior when users type quickly, cancel searches, or switch between search modes.
type SearchState =
| “closed”
| “keyword-idle”
| “keyword-loading”
| “keyword-results”
| “semantic-loading”
| “semantic-results”Request management
Lifecycle management helps control the system’s response when users type quickly.
// Cancel previous search if user types something new
if (keywordAbortControllerRef.current) {
keywordAbortControllerRef.current.abort();
}
const controller = new AbortController();
keywordAbortControllerRef.current = controller;UI and interaction details
Those things were all related to the technical implementation of search, but I also paid a lot of attention to the UI and interaction design. I repeated some of the same patterns across all the search prototypes I made, which included:
Progressing enhancement
In one prototype, the interface starts with immediate keyword results and then allows the user to upgrade to semantic search when need it.
Visual polish
The more advanced prototypes include touches like showing and hiding gradient masks to make transitions at the edges of scroll boundaries feel smoother:
// Hide the bottom fade gradient when user has scrolled to the end
className={cn(
"absolute bottom-0 h-8 bg-gradient-to-t from-white",
scrollState?.isAtBottom || isEndVisible
? "opacity-0"
: "opacity-100"
)}