Search as you type
The user types into a search box. Each keystroke could either kick off an expensive filter that blocks the input, or trigger a deferred filter that runs as low-priority work while the input stays responsive. The difference between the two — same dataset, same filter logic, same artificial work per keystroke — is whether the user feels they are typing or fighting.
This scenario sits in the 0–100 MS band. Card-Moran-Newell's Card, Moran & Newell 1983 ~100 ms perceptual frame is the upper bound for input responsiveness; Miller's Miller 1968 keystroke-echo tier is at 0.1 s for a reason. The naive side breaks both. The tuned side honours both.
Search as you type
Same dataset, same filter logic, same artificial work per keystroke. The naive side blocks input on every keystroke; the tuned side uses React 19's useDeferredValue to keep the input instant while the filter catches up.
Off
38 results
- Apple
- Apricot
- Avocado
- Banana
- Blackberry
- Blueberry
- Cherry
- Coconut
- Cranberry
- Date
- Dragon fruit
- Elderberry
- Fig
- Grape
- Grapefruit
- Guava
- Honeydew
- Kiwi
- Kumquat
- Lemon
- Lime
- Lychee
- Mango
- Mulberry
- Nectarine
- Orange
- Papaya
- Passion fruit
- Peach
- Pear
- Persimmon
- Pineapple
- Plum
- Pomegranate
- Raspberry
- Strawberry
- Tangerine
- Watermelon
On
38 results
- Apple
- Apricot
- Avocado
- Banana
- Blackberry
- Blueberry
- Cherry
- Coconut
- Cranberry
- Date
- Dragon fruit
- Elderberry
- Fig
- Grape
- Grapefruit
- Guava
- Honeydew
- Kiwi
- Kumquat
- Lemon
- Lime
- Lychee
- Mango
- Mulberry
- Nectarine
- Orange
- Papaya
- Passion fruit
- Peach
- Pear
- Persimmon
- Pineapple
- Plum
- Pomegranate
- Raspberry
- Strawberry
- Tangerine
- Watermelon
What is happening in the demo
Both sides use the same dataset (~38 fruit names) and the same filter cost (~80 ms of synchronous CPU per keystroke). The difference is how the filter runs.
The naive side runs the filter synchronously inside the input's onChange handler. The input value update and the result update both wait for the filter to finish. When you type fast — say, "stra" in three keystrokes — each keystroke blocks for ~80 ms before the next can register. The input visibly stutters; characters appear behind your fingers.
The tuned side wraps the filter in React 19's useDeferredValue. The filter still costs ~80 ms on the same input — but React schedules it as low-priority work and keeps the input render at high priority. The input updates instantly; the results catch up a frame or two later. While the catch-up is in flight, the result list dims to 60 % opacity (the isStale signal) and aria-busy="true" is set, so screen-reader users know a refresh is happening.
The total CPU work is identical. The user's experience is not.
What to tune
- Keystroke echo — character renders within ~50 ms. The input never blocks on the filter; deferred work runs at low priority via
useDeferredValue. - Stale state — previous results dim to 60 % opacity while the new filter runs.
aria-busy="true"flips on the result list. - Catch-up — fresh results swap in over ~150 ms. No spinner, no skeleton; the input itself is the affordance.
- Empty result — surface "No matches for
<query>" inline. Never blank. - Keyboard parity — Down-arrow into the list, Up-arrow back, Enter to commit, Escape to clear. Focus stays in the input during typing.
When perceived performance hurts you here
Search-as-you-type is a production surface in the consumption-vs-production sense — the user is reaching to act, not to read. Eizenberg's Eizenberg argument applies directly: a polished placeholder over the result panel does not save the experience if the input itself lags. Every perception trick on this surface needs to protect input responsiveness, not paper over its absence.
The most common failure mode in production: a debounce-only solution that does keep the input responsive but renders nothing during the debounce window. The user types, sees their characters echo instantly, then stares at a blank result panel for 300 ms while the debounce timer drains. That is half-tuned. Stale-while-revalidate fills the gap; the input echo alone does not.
Accessibility
aria-busyon the result list during stale-state refresh; flips tofalseon the catch-up.- Live region for result counts — when the result count changes substantively (e.g. from 5 to 1), announce politely. Avoid announcing every single keystroke.
- Keyboard arrow navigation for picking results without leaving the input. Down-arrow into the list, Up-arrow back, Enter to commit, Escape to clear.
- Don't trap focus during typing. The input is the user's home; results should never steal focus mid-type.
References
References · 3
- Card, Moran & Newell 1983
Card, S. K., Moran, T. P., & Newell, A. (1983). The Psychology of Human-Computer Interaction. Lawrence Erlbaum. ~100 ms perceptual frame; the input must respond inside that floor on every keystroke.
- Miller 1968
Miller, R. B. (1968). Response time in man-computer conversational transactions. Proceedings of the AFIPS Fall Joint Computer Conference, 33(I), 267–277. The keystroke-echo tier (~0.1 s) at the top of the response-time taxonomy.
- Eizenberg
Eizenberg, E. When Actual Performance Is More Important Than Perceived Performance (Medium). The argument that production surfaces (search inputs) cannot be saved by perception layers — input responsiveness is real performance, not perceived.