Search experience
Pre-alphaThe registry and the CLI are not published yet.Roadmap

Search experience

Complete search pattern, from the field to the results.

Draft

Specified, not implemented yet. The field is SearchBar; the rest is screen logic. The API may change.

SearchBar is only the field. The search experience is everything around it: what the screen shows before the user types, while they type, and when nothing matches.

Cities
LisbonPortugal
LondonUnited Kingdom
LyonFrance
Los AngelesUnited States
LimaPeru
LagosNigeria
ParisFrance
PortoPortugal
PragueCzechia
BerlinGermany
Recent
Your recent searches show up here.
No resultsCheck the spelling or try another city.
phase browse · keystrokes 0 · requests 0 · stale ignored 0

Tap the field: the list gives way to recent searches. Type a city. The request leaves after a short pause, not on every key, and the footer counts both. The fake server answers with a random delay, so type fast and watch stale ignored go up. Open a result to save the query. Try xyz for the no-results state, and Cancel to go back.

The screen, frame by frame:

Search
Cancel
focus

Empty field: recent searches, most recent first.

lis
Cancel
typing

A 250ms pause sends the request. The spinner takes the icon’s place.

lis
Cancel
Lisbon
Lisboa Oriente
Lismore
results

Matches, with the typed part in bold.

lsbon
Cancel
No results for "lsbon"Clear search
no results

Repeat the query, offer a way out.

focustypekeystrokepausetap a recent searchmatchesnothingtype againclearCancel · backscreenrecentdebounceloadingresultsno results

Recent searches

What the screen shows when the field is focused and still empty.

  • List the last queries, most recent first, as tappable rows built with Item.
  • Tapping a row runs that query again.
  • Let the user remove one entry, and clear the whole list.

Search history

Where recent searches come from.

  • Save a query when the user opens a result or submits, not on every keystroke.
  • Store it on the device and cap the list around 10 entries.
  • Move a repeated query to the top instead of adding it twice.

Debounce

Wait for a short pause in typing before sending the request, around 250 ms.

  • Show the loading state of SearchBar while the request runs.
  • Ignore responses to older queries, so results never flash back to a previous one.
  • A local list can filter on every keystroke, without debounce.

Cancel

Cancel leaves search mode.

  • It clears the field, closes the keyboard and returns to the previous screen.
  • The clear button inside the field only empties the query and keeps the user in search.

No results

The query matched nothing.

  • Repeat the query in the title: No results for "lsbon".
  • Offer a way out with Clear search.
  • No “create” action. The user was looking for something.

See No results in async content state.

Implementation

Conceptual
const [query, setQuery] = useState("");
const debounced = useDebouncedValue(query.trim(), 250);
// The query key changes with the text, so an older response never lands on a newer query.
const results = useQuery({
	queryKey: ["search", debounced],
	queryFn: () => api.search(debounced),
	enabled: !!debounced,
});

<SearchBar
	value={query}
	onChangeText={setQuery}
	loading={results.isFetching}
	onCancel={navigation.goBack}
/>;
{
	!query ? (
		<RecentSearches onPress={setQuery} />
	) : results.data?.length === 0 ? (
		<NoResults query={debounced} onClear={() => setQuery("")} />
	) : (
		<ResultList data={results.data} />
	);
}

Accessibility

  • Announce the number of results when they arrive (“12 results”), not while typing.
  • The clear button and each “remove” button in recents have their own label.
  • Cancel is reachable with the system back gesture too.