Bridge view
Search Input
A text input optimised for free-text search queries — typically pinned at the top of a search-driven page or in a navigation bar — with a leading magnifier icon, optional clear affordance, and optional results-preview popup. Distinct from Combobox by emitting a free-text query rather than committing a constrained value: search results live in a separate region (a search-results page or a results panel) rather than in the input's listbox. Distinct from plain Input by structuring the search-and-submit contract.
When to use
Use
For free-text search queries that route to a separate results region — site-wide search bars, in-page filter composers, search engines, document-finders, contact- pickers (where the result-set is long and unbounded). The user types intent, submits, and receives results elsewhere.
Avoid
For selecting a constrained value from a list — that is `Combobox` (commit-from-list semantics). For multi-value input where each value renders as an inline tag — that is `TagInput`. For free-text without search semantics — plain `Input`. For navigation menus styled as search — that is `MenuButton`.
Versus related
- combobox
`Combobox` commits a value from a constrained list (the input value is set to the selected option). `SearchInput` emits a free-text query that routes to a separate results region. They look similar with with-suggestions variant; the distinguisher is whether the popup contains options-to-commit (Combobox) or suggestions-to-fill-and-submit / result-previews-to- navigate (SearchInput).
- tag-input
`TagInput` accepts multiple discrete values rendered inline as tokens; `SearchInput` accepts a single free-text query. Multi-attribute search (filtering by multiple tags) is TagInput-shaped; single-query search is SearchInput-shaped. The two may compose — a search bar with both a TagInput for filters AND a SearchInput for query is a legitimate composite.
Figma↔Code mismatches
Where designer and developer worlds typically misalign on this component.
- 01 Figma
SearchInput drawn with a results listbox below it (Combobox-style)
CodeA SearchInput that emits a free-text query for a separate results page, with no inline listbox
ConsequenceDesigners may compose SearchInput mocks with combobox- style results inline in the listbox. Implementations following the design ship a Combobox; users committing via Enter expect to navigate to a search-results page, but instead a value is silently set in the input. The contract diverges: search emits queries; combobox commits values.
CorrectDistinguish at the canonical level. SearchInput emits a free-text query on submit (Enter, button click) — the results live in a separate region (search-results page, sibling panel). Combobox commits a value from a constrained list — the value is set inline. Document the with-suggestions variant explicitly: the popup shows previews of results that link to the full results page, NOT options to commit as the input value.
- 02 Figma
SearchInput drawn without a clear button
CodeSearchInput with no clear affordance, requiring users to manually delete the query
ConsequenceDesigners may omit the clear button for visual simplicity. Implementations following the design ship without clear; users typing partial queries must manually backspace or select-all-delete to clear. `<input type="search">` browsers provide a native clear affordance at the OS level which the design may not account for, leading to inconsistent visual treatment.
CorrectThe clear button is canonical when `hasClear: true` (default). For native `<input type="search">` the browser provides one for free; styled SearchInputs that override the native clear must provide their own. Document the canonical contract: clear visible when input is non-empty.
- 03 Figma
Magnifier icon drawn as a static decoration
CodeA magnifier icon that may be either decorative (leading) or interactive (submit button trailing)
ConsequenceDesigners may draw the magnifier as a leading visual decoration; developers ship it as a button. Or designers draw it as a button; developers ship it as a decoration. The clickable-vs-decorative status is not encoded in the Figma file.
CorrectThe leading icon (`anatomy.icon`) is decorative. The submit button (`anatomy.submit-button`) is a real button. Document the variant-driven choice: standard variant has both (decorative leading + optional trailing submit button); compact variant uses the magnifier as the submit button (the only icon).
- 04 Figma
Search suggestions drawn looking like search results
CodeSuggestions that auto-complete the query vs results that link to specific items
ConsequenceDesigners compose with-suggestions variant mocks that visually conflate "query autocompletion" with "result preview". Users clicking a suggestion expect either to navigate to a result OR to fill in the query and submit; the two intents are different.
CorrectThe popup hosts one of two distinct content types: query suggestions (text strings that, when activated, fill the input and submit) OR result previews (links that navigate directly to the result). Document the distinction; surface it visually (suggestions look like input completions; result previews look like list rows with title/description).
Variants, properties, states
Variants
Structurally different versions of the component.
standardcompactwith-suggestions Properties
The same component, parameterised.
| Property | Type |
|---|---|
hasIcon | boolean |
hasClear | boolean |
hasSubmit | boolean |
size | sm | md | lg |
States
Browser/user-driven (interactive) vs. app-driven (data).
| Kind | States |
|---|---|
interactive | hoverfocus-visibleactivedisabled |
data | emptyfilledbusy |
Figma ↔ Code property map
| Figma | Type | Code | Notes |
|---|---|---|---|
Variant | Variant | variant | Maps standard / compact / with-suggestions. |
Size | Variant | size | sm / md / lg. |
Has Icon | Boolean | hasIcon | Toggles the leading magnifier icon. Default true; false for compact variant where the magnifier is the submit button instead. |
Has Clear | Boolean | hasClear | Toggles the clear button. Default true; visible only when input has a value. |
Has Submit | Boolean | hasSubmit | Toggles the submit button. Default false (Enter submits); true for compact variant or for forms where explicit pointer-submit is preferred. |
Placeholder | Text | placeholder | Examples — "Search products…", "Search documents…". Avoid placeholder-only labelling. |
Disabled | Boolean | disabled | — |
Icon | Instance Swap | icon | Swap the magnifier glyph; rare to deviate from the canonical magnifier. |
State transitions
| From | To | Trigger |
|---|---|---|
empty | filled | User types into the input. Clear button (when `hasClear: true`) becomes visible; the canonical search-results pipeline begins (debounced query dispatch for with-suggestions variant; standard variant waits for explicit submit). |
filled | empty | User clears the input — via clear button, Escape key (closes results popup if open and clears value), form reset, or programmatic clear. |
filled | busy | For with-suggestions variant, the user types and the debounced query fires. Suggestions / preview-results request is in flight; the input shows a busy indicator (spinner or progress bar) and any open results popup shows a loading state. |
busy | filled | Async suggestions / results return. The popup renders the new results; busy indicator clears. |
Figma anatomy
| Slot | Figma type | Hint |
|---|---|---|
root | frame | Auto-layout horizontal frame with input and decorative slots; min-inline-size from variant |
icon | instance | Magnifier icon component instance; non-interactive in default variant |
input | instance | Text input component instance; typeahead/suggestion variant adds combobox semantics |
clear-button | instance | Icon button instance; visibility bound to "has value" state |
submit-button | instance | Icon button instance; magnifier glyph; visibility bound to "has submit button" property |
results-popup | frame | Floating frame anchored to input; visibility bound to suggestions/results state |
Code anatomy
| Slot | Code slot | Semantic |
|---|---|---|
root | root | search-region |
icon | icon | presentational |
input | input | searchbox |
clear-button | clear | button |
submit-button | submit | button |
results-popup | results-popup | listbox-or-region |
Cross-framework expression
| Framework | Structure mechanism | Variant mechanism |
|---|---|---|
| Web Components | A `<ui-search>` host wrapping `<input type="search">` plus optional slots for icon, clear, submit, results-popup; for with-suggestions variant, layer Combobox semantics on the input | attributes (`variant="compact"`, `has-icon`, `has-clear`, `has-submit`, `size="md"`); `data-state="empty|filled|busy"` for CSS |
| React | React Aria `useSearchField` (which provides the input plus clear-on-Escape contract) plus optional Combobox composition for suggestions; or a custom `<SearchInput>` wrapping `<input type="search">` plus icon/clear/submit slots | props with class-variance-authority for variant / size; `onChange(value)`, `onSubmit(value)`, `onClear()` callbacks |
| Angular (signals) | Angular form-control directive on `<input type="search">` plus optional CDK Listbox for suggestions; signal-based query and results state | input<'standard' | 'compact' | 'with-suggestions'>(); `[hasClear]`, `[hasSubmit]`, `[size]` host bindings |
| Vue | Custom `<SearchInput>` SFC wrapping `<input type="search">` with named slots; for suggestions variant, Headless UI `<Combobox>` composition | defineProps with literal-union types; `:variant`, `:has-clear`, `:size` props |
Events
queryChangesubmitclear
Form integration
- name attribute
- The internal `<input type="search">` carries the form `name` attribute. Native `<input type="search">` produces a string entry in the form's FormData under the configured `name`; the value is the current input text.
- FormData serialization
- Submit produces a single FormData entry (key=name, value=current input string). For form-nested SearchInputs where Enter triggers form submission, the search query submits as part of the broader form. For standalone SearchInputs (search bar in nav), the consumer captures the value via the `submit` event without form submission.
- form.reset()
- `form.reset()` restores the input to its `defaultValue`. For nested SearchInputs in a form being reset, the search query clears alongside the rest. Standalone SearchInputs not nested in a form respond only to programmatic `.value = ""` plus `clear` event.
- HTML5 validation
- `<input type="search">` is rarely required by canon — search inputs tolerate empty queries (which submit "show all" results). The `required` attribute is technically available but rarely meaningful. For with-suggestions variant where commit-from-listbox is wanted, layer Combobox's strict-mode `setCustomValidity()` plus `aria-invalid` for "must select from suggestions" enforcement.
Performance thresholds
typeaheadDebouncekeystroke-interval≥150msAsync typeahead-suggestion requests debounce keystrokes by ~150ms to avoid request floods. Below the perceptual threshold of feeling laggy (~200ms); suppresses 80%+ of in-flight stale requests on a 60–80 wpm typer. Mirrors the Combobox.asyncFilterDebounce threshold for consistency in the ecosystem.
minQueryLengthcharacters≥2charactersSuggestions are typically not surfaced for queries below 2 characters — single-character queries match everything and provide no useful narrowing. The threshold is canonical convention; some search backends require 3+ for performance reasons. Below the threshold, the popup remains closed.
Internationalisation
RTL · mirroring
Leading icon position moves from inline-start (visual left in LTR) to inline-start (visual right in RTL) via logical positioning. Clear button and submit button move from inline-end to inline-end (visual left in RTL). Magnifier glyph is direction-neutral; chevron glyphs (if used in suggestions) follow standard direction-neutral rotation. Input caret follows document direction; mixed-direction queries (typing Hebrew into a Latin-default field) honour the input's own `dir` attribute.
Text expansion
Placeholder text expands significantly in long-text languages — "Search products" → "Produkte suchen", "Поиск товаров", "Hae tuotteita". Allow input inline-size to grow; never truncate placeholder. Suggestion list inline-size matches input by default; long-text suggestions wrap to multiple lines or truncate with ellipsis (full text in `aria-label` for SR fallback).
Accessibility
| Slot | Accessibility hint | |
|---|---|---|
root | Apply `role="search"` (or `<search>` element where supported) when the input is the page's primary search landmark. For inline search inputs inside other regions (a filter inside a settings panel), no landmark role is needed — the input alone is sufficient. | |
icon | Decorative — `aria-hidden="true"`. The accessible name comes from the wrapping label or from `aria-label` ("Search products"); the magnifier glyph is not a substitute. | |
input | `<input type="search">` is the canonical default. For with-suggestions variant, layer the Combobox contract (`role="combobox"`, `aria-controls` referencing the results popup, `aria-activedescendant` for the highlighted suggestion). The accessible name comes from a paired `<label>` or `aria-label`. Avoid placeholder- only labelling. | |
clear-button | Real `<button>` with accessible name ("Clear" or "Clear search"). Activation clears the input value, returns focus to the input, and may reset associated results. Escape with focus on the input also clears (per APG Combobox guidance and `<input type="search">` platform default). | |
submit-button | Real `<button type="submit">` (when nested in a `<form>`) or `<button>` with explicit submit handler. Accessible name "Search" or "Submit search". Enter on the input also submits — the button is an additional affordance, not a replacement. | |
results-popup | For typeahead suggestions, `role="listbox"` plus `aria-activedescendant` on the input. For live result- preview (a search engine surfacing top hits), the popup is `role="region"` with an `aria-live="polite"` status announcing result counts. The two patterns are distinct and the popup's role differs accordingly. |
Accessibility acceptance
Keyboard walk
| Keys | Expected |
|---|---|
Tab | Focus enters the input. Subsequent Tab moves to the clear button (if present and visible), then to the submit button (if present), then out of the SearchInput. Disabled states skip from focus order. |
Enter (focus on input) | Submits the query — fires the canonical `submit` event. For with-suggestions variant where a suggestion is highlighted, Enter selects-and-submits the suggestion; without highlight, Enter submits the typed value verbatim. |
Escape (focus on input) | With suggestions popup open: closes the popup without clearing the query. With popup already closed: clears the input value (per `<input type="search">` platform default and APG Combobox guidance). |
ArrowDown / ArrowUp (focus on input, with-suggestions variant) | Opens the suggestions popup if closed; moves highlight to next / previous suggestion when open. DOM focus stays on the input; `aria-activedescendant` updates. |
Enter or Space (focus on clear button) | Activates clear; input value resets; focus returns to the input; results region resets to default. |
Enter or Space (focus on submit button) | Activates submit; equivalent to Enter on the input. |
Screen-reader announcements
| Trigger | Expected |
|---|---|
| Focus enters the input | SR announces the input's accessible name followed by "search" (from `<input type="search">` or `role="searchbox"`). For with-suggestions variant also "expanded false, has popup, listbox" until the popup opens. |
| User types into the input | Standard variant: no announcement (typing is the user's own input). With-suggestions variant: when debounced suggestions return, SR announces the result count via the popup's polite live region ("12 suggestions"). |
| Submit fires (Enter) | Page announces the search-results region (typically via focus moving to a results heading with `tabindex="-1"` and `aria-live` on the count statement). The SearchInput itself does not announce submit completion. |
| Clear activated | Input value clears silently (no SR announcement for the empty state). Subsequent re-focus on the input announces it as "<accessible name>, search". |
axe-core rules to assert
aria-input-field-namearia-required-attraria-valid-attr-valuecolor-contrastlandmark-unique
Common mistakes
#searchinput-as-combobox
SearchInput implemented as a Combobox with constrained values
The implementation uses Combobox semantics — committing from the listbox sets the input value. Users typing free-text searches that do not match any option get silently rejected on commit; the search query never fires.
SearchInput emits free-text queries; the results popup is for previewing, not for committing as the input value. Use `role="searchbox"` (input type="search") for the input; if the with-suggestions variant adds a listbox, the listbox items are clickable suggestions that fill-and-submit, not commit-and-set.
#searchinput-no-debounce
Suggestion request fires on every keystroke without debouncing
The with-suggestions variant fires a request on every character typed. Users with fast typing flood the backend; results race conditions render stale data.
Debounce typeahead requests by ~150ms. Below the perceptual threshold of feeling laggy; suppresses 80%+ of in-flight stale requests on a 60–80 wpm typer. Cancel in-flight requests when a newer one fires.
#searchinput-no-submit-on-enter
Enter key does not submit the query
The implementation captures Enter for some other purpose (closing the popup, blurring the input). Users pressing Enter expect to commit the search; nothing happens.
Enter on the input always submits the search query. For the with-suggestions variant where Enter-with-highlighted- suggestion may select-and-submit, Enter-without-highlight submits the typed query verbatim. The submit-button activation is equivalent.
#searchinput-clear-doesnt-restore-results
Clear button removes the query but leaves stale results
User clears the search; the results page or panel still shows the old results. The state diverges — the input is empty, but the results pretend the search is active.
Clear button activation must reset the associated results region back to its empty / default state. Document the canonical contract: query state and results state are coupled; clearing one clears the other.
#searchinput-results-popup-no-aria
With-suggestions popup has no `aria-live` or listbox structure
The popup renders suggestions but SR users hear nothing — no listbox role, no aria-live status. They type, get no feedback, and may submit empty queries without knowing suggestions exist.
For typeahead suggestions, the popup is `role="listbox"` with `aria-activedescendant` driving the announcement. For result-previews, the popup is `role="region"` with a polite live region announcing result counts ("12 results"). The two patterns are distinct; document both contracts.