Autocomplete — suggestions appearing as the user types — used to require the jQuery UI widget. Today the pieces are built into the platform: an ASP.NET Core action returns matching items as JSON, and the browser's native <datalist> element (or a few lines of fetch code) renders the suggestions. This tutorial builds both variants, plus the debounce pattern that keeps your server from being hammered on every keystroke.

The suggestions endpoint
public class CatalogController : Controller
{
private static readonly string[] AllProducts =
[
"Galaxy A15 Mobile", "Pixel 9", "iPhone 16",
"Air Pods", "Sony WH-1000XM5", "JBL Flip",
"Pen drive", "SSD 1TB", "MicroSD 256GB",
];
public JsonResult Suggest(string term) =>
Json(AllProducts
.Where(p => p.Contains(term ?? "", StringComparison.OrdinalIgnoreCase))
.Take(8));
}
Verified: GET /catalog/suggest?term=ss → ["SSD 1TB"].
In production this is a database query — with EF Core, Where(p => p.Name.Contains(term)).Take(8) translates to SQL LIKE. Always cap the results (Take) — an autocomplete needs eight good suggestions, not eight thousand.
Variant 1 — native datalist (zero JavaScript frameworks)
<datalist> gives you the dropdown UI for free; you only fill it:
<input id="product-search" list="product-suggestions"
placeholder="Search products…" autocomplete="off" />
<datalist id="product-suggestions"></datalist>
<script>
const input = document.getElementById('product-search');
const list = document.getElementById('product-suggestions');
let debounceTimer;
input.addEventListener('input', function () {
clearTimeout(debounceTimer);
const term = this.value.trim();
if (term.length < 2) { list.innerHTML = ''; return; }
debounceTimer = setTimeout(async () => {
const response = await fetch(`/catalog/suggest?term=${encodeURIComponent(term)}`);
if (!response.ok) return;
const suggestions = await response.json();
list.innerHTML = '';
for (const s of suggestions)
list.append(new Option(s));
}, 250);
});
</script>
Three details that matter:
- Debounce — the
setTimeout/clearTimeoutpair waits 250 ms after the last keystroke before calling the server. Typing "galaxy" fires one request, not six. - Minimum length — skip lookups under two characters; one-letter matches are noise and load.
encodeURIComponent— the term goes into a URL; encode it so spaces and special characters survive.
This is the version to reach for first: no library, accessible out of the box, works on mobile.
Variant 2 — custom dropdown with fetch
When you need richer suggestions than <datalist> allows (images, secondary text, highlight-as-you-type), render your own list — same endpoint, same debounce:
<div class="ac-wrap">
<input id="search" placeholder="Search products…" autocomplete="off" />
<ul id="search-results" hidden></ul>
</div>
<script>
const box = document.getElementById('search');
const results = document.getElementById('search-results');
let timer;
box.addEventListener('input', function () {
clearTimeout(timer);
const term = this.value.trim();
if (term.length < 2) { results.hidden = true; return; }
timer = setTimeout(async () => {
const items = await (await fetch(`/catalog/suggest?term=${encodeURIComponent(term)}`)).json();
results.innerHTML = '';
for (const item of items) {
const li = document.createElement('li');
li.textContent = item;
li.addEventListener('click', () => {
box.value = item;
results.hidden = true;
});
results.append(li);
}
results.hidden = items.length === 0;
}, 250);
});
document.addEventListener('click', e => {
if (!e.target.closest('.ac-wrap')) results.hidden = true;
});
</script>
Style #search-results as an absolutely-positioned list under the input. For a production-grade custom widget also handle arrow-key navigation and aria-* attributes — or use <datalist>, which does all of that for free.
The jQuery UI version (legacy)
If you maintain a page that already uses jQuery UI's autocomplete, it plugs into the same endpoint:
$('#product-search').autocomplete({
source: function (request, response) {
$.getJSON('/catalog/suggest', { term: request.term }, response);
},
minLength: 2,
delay: 250
});
Nothing on the server changes between all three variants — one JSON endpoint serves them all, which is exactly the point of separating the API from the UI. For new work, skip the jQuery UI dependency and use the datalist.
Related
- jQuery AJAX call with JsonResult — the underlying fetch/JSON patterns
- Cascading dropdowns with JsonResult — the same idea driving selects
Comments (0)
No comments yet — be the first to share your thoughts.