Dev 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.

Highlight
Fig 1.1 · Search Input · Dev view
Dev

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
Both

Variants, properties, states

Variants

Structurally different versions of the component.

standardcompactwith-suggestions

Properties

The same component, parameterised.

PropertyType
hasIconboolean
hasClearboolean
hasSubmitboolean
sizesm | md | lg

States

Browser/user-driven (interactive) vs. app-driven (data).

KindStates
interactive
hoverfocus-visibleactivedisabled
data
emptyfilledbusy
Both

State transitions

FromToTrigger
emptyfilledUser 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).
filledemptyUser clears the input — via clear button, Escape key (closes results popup if open and clears value), form reset, or programmatic clear.
filledbusyFor 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.
busyfilledAsync suggestions / results return. The popup renders the new results; busy indicator clears.
Dev

Cross-framework expression

FrameworkStructure mechanismVariant 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
Both

Events

  1. queryChange
    Payload
    `{ value: string }`. Fires on every input change. For with-suggestions variant, this is the source for debounced suggestion requests. For standard variant, consumers may ignore it and only handle `submit`.
    Web Components
    `queryChange` CustomEvent on the host with `event.detail = { value }`.
    React
    `onChange(value: string)` controlled-pattern callback.
    Angular Signals
    `output<string>('queryChange')`; in reactive forms, the FormControl `valueChanges` observable.
    Vue
    `@update:modelValue` for `v-model` on the input value.
  2. submit
    Payload
    `{ value: string }`. Fires when the user commits the query — Enter on the input, click on the submit button, or programmatic submit. Distinct from `queryChange` because submit signals "search now"; queryChange signals "user is still typing".
    Web Components
    `submit` CustomEvent with `event.detail = { value }`.
    React
    `onSubmit(value: string)` callback. For with-suggestions variant where Enter-with-highlight selects, the submit fires with the selected suggestion's value.
    Angular Signals
    `output<string>('submit')`.
    Vue
    `@submit` event with payload `{ value }`. Form-nested searches may emit native `submit` instead.
  3. clear
    Payload
    No payload (empty object or undefined). Fires when the user clears the query — clear button click, Escape key with focus on input, programmatic clear. Pairs with `queryChange` (which fires with `value: ""`).
    Web Components
    `clear` CustomEvent on the host.
    React
    `onClear()` callback.
    Angular Signals
    `output<void>('clear')`.
    Vue
    `@clear` event.
Dev

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.
Dev

Performance thresholds

  • typeaheadDebouncekeystroke-interval150ms

    Async 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.

  • minQueryLengthcharacters2characters

    Suggestions 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.

Both

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.
Both

Accessibility acceptance

Keyboard walk

KeysExpected
TabFocus 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

TriggerExpected
Focus enters the inputSR 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 inputStandard 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 activatedInput 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-name
  • aria-required-attr
  • aria-valid-attr-value
  • color-contrast
  • landmark-unique
Dev

Common mistakes

#searchinput-as-combobox

SearchInput implemented as a Combobox with constrained values

Problem

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.

Fix

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

Problem

The with-suggestions variant fires a request on every character typed. Users with fast typing flood the backend; results race conditions render stale data.

Fix

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

Problem

The implementation captures Enter for some other purpose (closing the popup, blurring the input). Users pressing Enter expect to commit the search; nothing happens.

Fix

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

Problem

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.

Fix

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

Problem

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.

Fix

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.

Figma↔Code mismatches
  1. 01
    Figma

    SearchInput drawn with a results listbox below it (Combobox-style)

    Code

    A SearchInput that emits a free-text query for a separate results page, with no inline listbox

    Consequence

    Designers 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.

    Correct

    Distinguish 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.

  2. 02
    Figma

    SearchInput drawn without a clear button

    Code

    SearchInput with no clear affordance, requiring users to manually delete the query

    Consequence

    Designers 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.

    Correct

    The 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.

  3. 03
    Figma

    Magnifier icon drawn as a static decoration

    Code

    A magnifier icon that may be either decorative (leading) or interactive (submit button trailing)

    Consequence

    Designers 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.

    Correct

    The 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).

  4. 04
    Figma

    Search suggestions drawn looking like search results

    Code

    Suggestions that auto-complete the query vs results that link to specific items

    Consequence

    Designers 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.

    Correct

    The 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).