All writing

Angular · UX

Building a Global Search That Doesn't Feel Bolted On

Cross-module search is easy to ship and easy to ship badly. The difference is almost entirely in what happens after the keystroke.

Jaymin Maheta 7 min read
Share:
Search interface with results list

Most "global search" is per-module search

A search box in the header that only queries whatever module you're currently viewing isn't global search — it's a filter with a bigger ambition in its label. Real global search means one query, one keystroke to open it from anywhere, and results spanning every entity type a user might be looking for: clients, projects, documents, tasks, invoices.

That's a harder problem than it looks, because each of those entity types usually lives behind a different API, a different permission model, and a different "open this result" navigation path.

What separates good search from a box that queries an API

One global keyboard shortcut

Cmd/Ctrl+K opens search from anywhere in the app, focus already in the input. If a user has to reach for the mouse to start searching, the feature will be underused.

Group results by entity type

A flat list mixing clients, invoices and documents forces users to read every row. Grouped, labeled sections let them jump straight to the type they're after.

Debounce, don't throttle the first keystroke

Debounce requests as the user types, but never delay the very first character's request — that first response is what convinces someone the feature is fast.

Preview before navigating

A hover or arrow-key preview of a task or document saves a full page navigation for the common case of "just checking," and keeps the search panel open for the next query.

Respect permissions in the index, not the UI

Filtering results a user can't access after they've already appeared on screen is both a security bug and a confusing flash of content. Filter server-side, before the response is built.

Design the zero-results state

A blank panel after a query reads as broken. Suggest a spelling correction, a broader term, or a fallback action — "create a new client named…" — instead of silence.

The navigation problem nobody plans for

Every entity type search returns needs its own "go here" route, and those routes rarely line up with a single, predictable URL pattern once permissions, tabs and nested detail views are involved. Build a small routing resolver per entity type early — a lookup table from result type to a route-building function — rather than hardcoding navigation logic inside the search component itself.

That separation is what lets a new entity type get search support in an afternoon instead of a sprint: register its resolver, index it server-side, and the search UI needs no changes at all.

The takeaway

Global search earns its name through keyboard access from anywhere, grouped and previewable results, permission filtering at the source, and a navigation layer decoupled from the search component itself. Get those right and it stops feeling like a filter with a bigger label — it becomes the fastest way through the product.

Let's talk about your frontend