Async autocomplete (combobox)
A text input with a dropdown of server-fetched suggestions. Debounced, cancellable, keyboard-navigable.
#When to use
Large option sets that can't feasibly ship to the client (user picker, city picker, product SKU). For small static sets, native or the simpler with client-side options is lighter.
#Markup
Use with the search-url attribute — it wires HTMX-style server fetches under the hood:
<noundry-combobox label="Assignee"
placeholder="Search teammates…"
search-url="/api/users/search"
search-placeholder="Type 2+ characters" />
Or roll your own input + results pattern with raw HTMX:
<input type="search" name="q" placeholder="Search teammates…"
hx-get="/api/users/search"
hx-trigger="keyup changed delay:300ms"
hx-target="#assignee_options"
hx-swap="innerHTML" />
<ul id="assignee_options"></ul>
#Handler
The server contract returns JSON: [{value, text}, ...] for the query ?q=:
app.MapGet("/api/users/search", async (string? q, IUserService users, CancellationToken ct) =>
{
var matches = await users.SearchAsync(q ?? "", limit: 10, ct);
return Results.Ok(matches.Select(u => new { value = u.Id, text = $"{u.Name} ({u.Email})" }));
});
For the raw-HTMX variant, return an HTML fragment instead:
public async Task<IActionResult> OnGetSearchAsync(string? q, CancellationToken ct)
{
var matches = await _users.SearchAsync(q ?? "", limit: 10, ct);
return Partial("_UserOptions", matches);
}
@model IEnumerable<UserSummary>
@foreach (var u in Model)
{
<li data-value="@u.Id" data-label="@u.Name">@u.Name <small>@u.Email</small></li>
}
#Cancellation
HTMX cancels the prior request automatically when a new one fires for the same triggering element (hx-sync defaults to "drop in-flight"). The server still has to handle "client disconnected" gracefully — which ASP.NET Core does out of the box via HttpContext.RequestAborted.
#Loading state
HTMX adds the class htmx-request to the triggering element while a request is in flight. Style it:
input.htmx-request { background-image: url(/spinner.svg); background-repeat: no-repeat; background-position: right 8px center; }
#Related
- Debounced live search — same primitive, typically applied to a result list rather than a popover.
- Cascading selects — related pattern for dependent dropdowns.