Autocomplete

Free

Searchable dropdown with grouping, sublabels, icons, disabled options, keyboard navigation and query highlighting.

</>sv-autocomplete autocompletecomboboxsearch
import { SvAutocompleteComponent } from '@styloviz/forms';

Demo & usage

Basic usage

Usage example — open the gallery for the full interactive demo.

An interactive demo for this component lives in the gallery.

Open live demo

More examples

Clearable with loading

example.htmlAngular
<sv-autocomplete
  [options]="results"
  [clearable]="true"
  [loading]="isFetching"
  placeholder="Search users…"
  (inputChange)="fetch($event)"
  [(ngModel)]="user" />

Grouped sections

Give options a `group` and set `grouped` to render labelled sections.

example.htmlAngular
<sv-autocomplete [options]="options" [grouped]="true" [(value)]="value" />

When to use

  • Typing to filter and pick a single value from a large or remote dataset.
  • You want query highlighting, grouping and async-friendly inputChange.
  • For multiple selections use Tag Input; for a fixed short list use Select Dropdown.

Async states

  • loading
  • error
  • retry
  • partial

Inputs

NameTypeDefaultDescription
optionsrequiredreadonly AutocompleteOption[]—Options to filter and choose from (supports groups, sublabels, icons).
valuestring (two-way)''Selected value (two-way).
placeholderstring'Search…'Placeholder shown when empty.
sizeAutocompleteSize'md'Field size: sm, md or lg.
variantAutocompleteVariant'default'Visual variant.
disabledbooleanfalseDisable the field.
labelstring''Accessible label for the field.
clearablebooleantrueShow a clear (×) button when a value is selected.
maxItemsnumber8Maximum suggestions rendered at once. ⚠️ **IT IS A HARD BOUNDARY AND IT USED TO BE A SILENT ONE**. A query matching 500 options showed eight, offered eight to the keyboard, and said nothing at all about the other 492 — so a user whose entry was 300th in the list concluded it was not there. The panel now discloses the truncation; see `truncationNote`.
totalCountnumber | nullnullHow many results exist in total, when the component cannot know. With `serverSide` the supplied `options` are already one page of a larger answer, so counting the array understates it. A host that knows the real total passes it here and the disclosure reads "8 of 4,312" instead of "8 of 20". Ignored without `serverSide`, where the array IS everything.
groupedbooleanfalseRender results in labelled sections using each option's `group`.
loadingbooleanfalseShow a loading state (e.g. while fetching async results).
stalebooleanfalseThe options on screen answer an **older** query — a newer request is still in flight. Distinct from `loading`, which cannot separate the two cases it covers: the first fetch (nothing on screen — show a skeleton) and a refinement of results already visible (keep them, and say they are provisional). Blanking the panel on the second is the most jarring thing a combobox can do; showing the old list with no indication is a quiet lie. Bind `svSuggestController(...).stale` from `@styloviz/data`, or `createRequestGuard().stale` from `@styloviz/core`. The results dim and report `aria-busy` while staying fully interactive.
noResultsLabelstring'No results found'Text shown when no options match.
serverSidebooleanfalseLet the server do the matching. The supplied `options` are shown exactly as given — filtering them again hides entries the server matched on fields the component cannot see.
debounceMsnumber300Delay before `inputChange` fires, in ms. `0` emits immediately.
minCharsnumber0Characters required before a lookup is sent. Below this the panel says so instead of showing a misleading "No results found".
errorSvDataError | string | nullnullFailure loading the options, rendered inside the panel.
iconstring'search'Name of an icon in `@styloviz/icons`, e.g. `'search'`. This used to be a raw `<path d="…">` string. That put SVG geometry in every consumer's config and skipped the library's sizing, stroke and dark-mode handling — pass a name instead.
errorMessagestring''Client-side validation message.
serverErrorSvServerErrornullA rejection reported by the server about this value. Self-expiring.

Outputs

EventPayload typeDescription
selectedAutocompleteOptionEmitted with the chosen option.
clearedvoidEmitted when the selection is cleared.
inputChangestringEmitted on every query change (debounce upstream for async sources).
retryvoidThe host should re-run the failed lookup.
valueChangestringEmits the new `value` whenever it changes — the output half of `[(value)]`.

Wire it to your API

This component is headless about transport: it renders what you give it and reports what the user asks for. Nothing below requires @styloviz/data — drive it from whatever you already use.

Template

Bound to the names this component actually exposes.

usage.htmlAngular
<sv-autocomplete
  [serverSide]="true"
  [options]="data.items()"
  [loading]="data.loading()"
  [error]="data.error()"
  [totalCount]="data.total()"
  (retry)="data.reload()"
/>

Host class

Drive the three async inputs from your own fetch.

host.component.tsTypeScript
// This component reports its state but does not emit a query, so drive
// it from your own fetch and bind the three async inputs.
readonly items = signal<Row[]>([]);
readonly loading = signal(false);
readonly error = signal<SvDataError | null>(null);

async load(): Promise<void> {
  this.loading.set(true);
  this.error.set(null);
  try {
    this.items.set(await this.api.fetchRows());
  } catch (e) {
    this.error.set(toSvDataError(e));
  } finally {
    this.loading.set(false);
  }
}
  • This component does not emit a query, so it renders what you give it. Fetch on your own trigger and bind `options`, `loading` and `error`.
  • `serverSide` switches **off** the built-in filtering, sorting and paging. Leave it false and the component works entirely from the array you pass — which is what you want for a few hundred rows.
  • `debounceMs` (default 300) collapses a burst of typing into one request. Set it to 0 only when the query is driven by clicks.
  • Normalize failures with `toSvDataError()` from `@styloviz/core`. It derives `retryable` from the status, so a 404 does not get a "Try again" button that can only fail again.

Full walkthrough: Connecting to a backend.

Customization & theming

  • `maxItems` (default 8) is a hard boundary, and it used to be a silent one: a query matching 500 options showed eight, offered eight to the keyboard and said nothing about the other 492 — so a user whose entry was 300th concluded it was not there. The panel now discloses it (“Showing 8 of 500 — keep typing to narrow”) as a `role="presentation"` footer row plus a `role="status"` announcement outside the listbox. ⚠️ Deliberately NOT `aria-setsize`: the unrendered options are genuinely unreachable, so declaring 500 would promise items no key can get to — worse than saying nothing.
  • With `serverSide`, pass `totalCount` so the disclosure reads “8 of 4,312” rather than counting the one page you handed over; the “keep typing” advice is dropped there, because what a narrower query returns is the host’s endpoint to promise, not this component’s.
  • Options with sublabels, icons and disabled state; query highlighting and a configurable no-results message.
  • Set `grouped` to render results in labelled sections by each option's `group` (keyboard nav and active-descendant stay correct across sections).
  • Clearable, loading state, `maxItems` cap and three sizes; named via `label`.
  • Two-way bind `[(value)]` or a reactive control (CVA); listen to `selected`, `cleared`, `inputChange`.

Best practices

  • Do: Show a `loading` state and a clear no-results message for async sources.
  • Do: Debounce `inputChange` before hitting the server.
  • Tip: Make the field `clearable` so users can reset the selection easily.

Accessibility

  • Proper ARIA combobox on the `<input>`: `aria-expanded`, `aria-controls` and `aria-activedescendant` track the highlighted option (options carry per-instance ids).
  • Arrow keys move, Home/End jump, Enter selects, Esc closes; the input is named by `label` or `placeholder`.
  • Implements ControlValueAccessor — works with `[(ngModel)]`, `[formControl]` and `formControlName`.

Feedback on Autocomplete

Spotted a gap in this page, or something that didn't work as described? Comments are GitHub Discussions in the public styloviz/community repo, so posting needs a GitHub account.

Loads giscus.app and GitHub — nothing is fetched until you click.