Search
Three shapes of search, and the one thing every product gets wrong — the screen shown when nothing was found.
What it solves
Search is not one thing. «Find me a record», «pick one of too many options» and «run a command I know the name of» look alike and behave differently — different input, different results, different failure.
The failure is where the difference stops mattering: every one of the three has a state where nothing was found, and that state is the least designed screen in most products.
Anatomy
Three shapes, and the library states the condition for each:
- A string is being typed —
GrInput. Plain search over a page or a list, where results appear elsewhere and the field only collects the query. - There are too many options for a list —
GrAutocomplete. A combobox: type-ahead over options, async loading, a choice at the end. The result is a value, not a page. - Commands are many and scattered —
GrCommandPalette. ⌘K over what the app can do, for someone who knows the name of the thing and does not want to look for its button.
Around all three sit the same four:
- filters —
GrChipGroupfor a row of them,GrSegmentedfor switching the view of the same results; - waiting —
GrSkeletonwhile the first results are on their way; - nothing found —
GrEmptyState, which is a different screen from «empty from the start» and is discussed below; - too many —
GrPagination.
Example
Do and don’t
Do — Say what was searched and how many were found
«7 results for select» does two jobs: confirms the query arrived intact, and sets expectations before the eye starts down the list. Both matter most when the number is small.
Don’t — An empty list as the answer to «nothing found»
A blank area under a search box is indistinguishable from a broken search. The person cannot tell «no matches» from «still loading» from «it crashed» — and all three need different reactions from them.
Do — Offer the next move when nothing matched
Nothing found is a question, not a full stop: show the query, suggest dropping a
filter, offer the nearest thing. GrEmptyState exists for exactly this screen,
and the screen deserves the same care as the results.
Don’t — Reuse the «no data yet» screen for «no matches»
«Nothing here yet — create the first one» is right for a new account and wrong after a search: nothing was created because nothing matched, and the button offers the wrong thing. Two states, two texts.
Do — Let Tab leave the combobox
In GrAutocomplete, Tab closes the panel and passes focus on down the document.
Someone filling a form is moving through fields, and search is one of them — not
a place to be held.
Don’t — Search on every keystroke without a leash
A request per character is four requests for «sele» and three answers arriving out of order. Debounce, and cancel what was overtaken — otherwise the list shows the answer to a query the person has already finished typing over.
Accessibility
A combobox is a pattern with a name. GrAutocomplete implements it: arrows
walk the options, the active option is announced as active rather than only
highlighted, and the input keeps focus while the list is being walked.
The number of results must be announced. Sighted people see the list appear; a screen reader needs to be told — a live region carrying «7 results» is the whole of it, and its absence makes search silent.
Loading is a state, not a decoration. Between the last keystroke and the
first result there is a moment where the answer is unknown, and it must be
perceivable — GrSkeleton reserves the shape, and the text says what is
happening.
This portal measures its own search the same way it recommends: the share of queries returning nothing is a tracked number, and it feeds the content roadmap. A search that finds nothing is not a bug in the search — it is a page that does not exist yet.
Components in this pattern
The list is built from frontmatter, not from links in the prose: renaming a slug does not lose it.
GrInputformsGrAutocompleteformsGrCommandPalettenavigationGrChipGroupdataGrSegmentedformsGrEmptyStatefeedbackGrSkeletonfeedbackGrPaginationnavigation
Keyboard
Contracts come from the library. The pattern does not restate them — it points at them.
GrInputComponent contractGrAutocompleteComponent contractGrCommandPaletteComponent contractGrChipGroupComponent contractGrSegmentedComponent contractGrPaginationComponent contract