Choice & selection

AppAutocomplete

Pick one value from a long or remote list by typing — the field narrows as you go.

<app-autocomplete label="City" name="city">…</app-autocomplete>

An autocomplete lets a user pick a single value from a list too long to scroll — a city, a customer, a product — by typing toward it. The field filters as they type, so they land on the right option in a keystroke or two instead of hunting through a giant dropdown.

Its look and behaviour come from the shared @webority/theme and the underlying <wui-autocomplete> element, so the Razor <app-autocomplete> and the React <AppAutocomplete> render identically on every Webority surface.

Playground

Every attribute, live. Each change re-renders the real tag helper on the server, so the markup underneath is the exact call that produced the field above it.

Mumbai Delhi Bengaluru Hyderabad Chennai Kolkata Pune Ahmedabad Jaipur Surat
<app-autocomplete label="City" name="city">
    <app-option value="mumbai">Mumbai</app-option>
    <app-option value="delhi">Delhi</app-option>
    <app-option value="bengaluru">Bengaluru</app-option>
    <app-option value="hyderabad">Hyderabad</app-option>
    <app-option value="chennai">Chennai</app-option>
    <app-option value="kolkata">Kolkata</app-option>
    <app-option value="pune">Pune</app-option>
    <app-option value="ahmedabad">Ahmedabad</app-option>
    <app-option value="jaipur">Jaipur</app-option>
    <app-option value="surat">Surat</app-option>
</app-autocomplete>
remote switches between static options and the debounced-fetch modeUnchecked, the real tag helper is composed with <app-option> children (static filter); checked, it's given remote="true" plus a page-side search handler, as wired below for this preview.
Menu open motion product-wide via data-wui-menu-motion — pick one, then open the menus below

Static filter

Pass an options array and the field filters it client-side. Best when the whole list is already in memory — up to a few hundred rows.

Client-side filtering over options
Compose the options as <app-option> children; the field matches as the user types.
Mumbai Delhi Bengaluru Hyderabad Chennai Kolkata Pune Ahmedabad Jaipur Surat
Client-side filter over a static list.
Show code
<app-autocomplete label="City" name="city" placeholder="Type a city…">
    <app-option value="mumbai">Mumbai</app-option>
    <app-option value="delhi">Delhi</app-option>
    <app-option value="pune">Pune</app-option>
    <!-- … -->
</app-autocomplete>

Remote search

Pass an async loadOptions instead of options and the field becomes a search box: it debounces typing, shows a spinner while your promise is in flight, then lists what came back.

Debounced fetch with a spinner
Set remote="true", then a page script answers the search event — set .loading, fetch, set .options.
Debounced search with a loading spinner.
Show code
<app-autocomplete remote="true" label="City" name="city"
                  placeholder="Type to search…" min-chars="1"></app-autocomplete>

<script>
  var el = document.getElementById('city');
  el.addEventListener('search', function (e) {
    el.loading = true;
    api.searchCities(e.detail.query).then(function (rows) {
      el.options = rows;   // [{ value, label }]
      el.loading = false;
    });
  });
</script>
Remote mode debounces for youThe field waits debounce ms (250 by default) after the last keystroke before firing the search event, and won't fire until min-chars characters are typed. That keeps one request per pause instead of one per letter — your API isn't hammered, and the spinner shows only while a real fetch is running.

Freeform entry

Set allow-custom="true" and a typed value that matches no option is offered as its own row — pick it (or press Enter) to commit the typed text itself as the value.

Add a tag that doesn't exist yet
The element fires change with the typed text as the value; a page script can inspect the committed value however it needs.
Urgent Billing Onboarding
Type a new tag and press Enter to add it.
Show code
<app-autocomplete label="Tag" name="tag" allow-custom="true">
    <app-option value="urgent">Urgent</app-option>
    <app-option value="billing">Billing</app-option>
</app-autocomplete>
Commits on Enter or on blurA typed value that matches nothing commits as custom either by pressing Enter with no row highlighted, by clicking the Add "…" row, or by leaving the field (blur) with unmatched text still in it.

Sizes

size takes sm or lg (md is the default), matching AppInput and AppSelect so an autocomplete lines up with the fields beside it.

Small, medium, large
Mumbai Delhi Bengaluru Hyderabad Chennai Kolkata Pune Ahmedabad Jaipur Surat
Mumbai Delhi Bengaluru Hyderabad Chennai Kolkata Pune Ahmedabad Jaipur Surat
Mumbai Delhi Bengaluru Hyderabad Chennai Kolkata Pune Ahmedabad Jaipur Surat
Show code
<app-autocomplete label="Small" name="c" size="sm">…</app-autocomplete>
<app-autocomplete label="Medium (default)" name="c">…</app-autocomplete>
<app-autocomplete label="Large" name="c" size="lg">…</app-autocomplete>

States

Surface a validation message with error, and mark a mandatory field with required.

Required with an error
error replaces the hint and marks the field invalid; required renders the asterisk.
Mumbai Delhi Bengaluru Hyderabad Chennai Kolkata Pune Ahmedabad Jaipur Surat
Choose a city to continue.
Show code
<app-autocomplete label="City" name="city" required="true"
                  error="Choose a city to continue.">
    <app-option value="mumbai">Mumbai</app-option>
    <!-- … -->
</app-autocomplete>

When to use

Use it when

  • The user picks one value from a list too long to scroll comfortably (dozens or more).
  • The list lives on the server — pass loadOptions and fetch only the matches the user types toward.
  • Users know the option by name and can type it faster than they can find it in a dropdown.

Reach for something else when

  • The user must pick several values. → AppMultiSelect
  • There are only 2–5 fixed, exclusive options. → AppRadio
  • The list is short and closed (6–15 options). → AppSelect
  • It is a free-text search that navigates or filters a page. → AppSearch

Best practices

Do
Mumbai Delhi Bengaluru Hyderabad Chennai Kolkata Pune Ahmedabad Jaipur Surat
A long list users know by name — type-to-find lands them on the right row in one or two keystrokes.
Don't
Small Medium Large
Three fixed options do not need a search box — the field is heavier than the choice.
Do
For a handful of exclusive options, radios show every choice at once — no typing, no dropdown.
Don't
Reserve the remote search for genuinely large or server-side lists, where loadOptions fetches only matches.

Every option

The whole surface of the component, one cell per value — including the modes and states a normal example doesn't cover: the loading spinner, an always-empty result set, a rejected search, and every field state.

mode

options — static filter
Mumbai Delhi Bengaluru Hyderabad Chennai Kolkata Pune Ahmedabad Jaipur Surat
remote="true" — debounced
loading (forced for the demo)
remote — always empty (type anything)
Every query resolves to zero results.
remote — rejects (falls back to empty)
The search always fails here — same visual as no matches.
remote — min-chars="3"
Search fires only once 3+ characters are typed.

size — every value

size="sm"
Mumbai Delhi Bengaluru Hyderabad Chennai Kolkata Pune Ahmedabad Jaipur Surat
size="md"
Mumbai Delhi Bengaluru Hyderabad Chennai Kolkata Pune Ahmedabad Jaipur Surat
size="lg"
Mumbai Delhi Bengaluru Hyderabad Chennai Kolkata Pune Ahmedabad Jaipur Surat

allow-custom

(default) — no freeform entry
Urgent Billing Onboarding
allow-custom="true" — offers an Add "…" row
Urgent Billing Onboarding

field state

disabled
Mumbai Delhi Bengaluru Hyderabad Chennai Kolkata Pune Ahmedabad Jaipur Surat
required
Mumbai Delhi Bengaluru Hyderabad Chennai Kolkata Pune Ahmedabad Jaipur Surat
error
Mumbai Delhi Bengaluru Hyderabad Chennai Kolkata Pune Ahmedabad Jaipur Surat
Choose a city to continue.
hint
Mumbai Delhi Bengaluru Hyderabad Chennai Kolkata Pune Ahmedabad Jaipur Surat
Start typing to filter.
error + hint (error wins)
Mumbai Delhi Bengaluru Hyderabad Chennai Kolkata Pune Ahmedabad Jaipur Surat
Choose a city to continue.
required + error
Mumbai Delhi Bengaluru Hyderabad Chennai Kolkata Pune Ahmedabad Jaipur Surat
Required.
value="mumbai" — populated
Mumbai Delhi Bengaluru Hyderabad Chennai Kolkata Pune Ahmedabad Jaipur Surat
help / help-label
Mumbai Delhi Bengaluru Hyderabad Chennai Kolkata Pune Ahmedabad Jaipur Surat
long option label (overflow)
The Grand International Convention & Exhibition Centre, Sector 44 Riverside Community Hall Downtown Rooftop Terrace

Attributes

AttributeTypeDefaultDescription
labelstring—Field label above the control.
namestring—Field name — also the element id the label points at.
valuestring—Initial value seed. With asp-for the bound model value is used instead, so a validation redisplay repopulates the field.
placeholderstringSearch…Placeholder text.
remoteboolfalseSwitches the field to the debounced-fetch mode; wire a search handler.
min-charsint0Minimum characters before a search fires (React minChars; defaults to 1 automatically when remote is set).
debounceint250Debounce (ms) for a remote search. Not a typed attribute on this tag helper — passes through to the element as an author attribute.
sizestring—Control size: sm / lg. Omit for the default (md).
allow-customboolfalseOffers the typed text as an "Add "…"" row when it matches no option (React allowCustom); committing it sets that raw text as the value.
disabledboolfalseDisables the field.
requiredboolfalseRenders the required asterisk on the label.
errorstring—Validation message; replaces the hint and marks the field invalid.
hintstring—Helper text below the field when there is no error.
helpstring—Info-tip content shown beside the label.
help-labelstringMore informationaria-label for the info-tip button (React helpLabel).
<app-option>value, selected—Child options for the static list — value attribute + label as inner content.
input-classstring—Extra classes on the control itself (the wui-autocomplete) — the React className counterpart. The plain class attribute merges onto the .field wrapper instead.