twitter/typeahead.js
Typeahead.js is Twitter's fast, flexible autocomplete library, pairing the Bloodhound suggestion engine with a jQuery plugin for local and remote datasets. It offers customizable templates, smart filtering, and easy integration for search-as-you-type inputs.
Typeahead.js ships two layers that you will almost always use together:
Minimal wiring:
<link rel="stylesheet" href="typeahead.css">
<script src="jquery.js"></script>
<script src="typeahead.bundle.js"></script>
var states = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
local: [{ name: 'Alabama' }, { name: 'Alaska' }]
});
$('.typeahead').typeahead(null, {
name: 'states',
displayKey: 'name',
source: states
});
Where to look first:
typeahead.bundle.js (or typeahead.bundle.min.js) – one file gives you both Bloodhound and Typeahead. Prefer the bundle unless you have a reason to split.Templates and Events sections – these describe ~90% of the customization you will actually do.displayKey – the single most important config after source. Get this right and the rest falls into place.First use case that almost every project hits: a search box with a remote JSON endpoint. That means a Bloodhound instance with remote: { url, wildcard, transform }, a typeahead call pointed at it, and minLength to keep the request count sane.
Watch the version: 0.11.x is the current API (displayKey, synchronous source functions, async option on Bloodhound datasets). If you copy 0.10-era snippets you will get display vs displayKey bugs. Check the version in the file header before trusting any tutorial.
Bloodhound lets a single typeahead consume multiple sources. Use it:
var people = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
prefetch: { url: '/api/people.json', cache: true },
remote: { url: '/api/people?q=%QUERY', wildcard: '%QUERY' }
});
$('#search').typeahead(
{ hint: false, highlight: true, minLength: 2 },
{ name: 'people', displayKey: 'name', source: people, limit: 8 },
{ name: 'projects', displayKey: 'title', source: projectEngine }
);
Pattern: prefetch for stable reference data, remote for user-typed queries, local for tiny static lists. Prefetch + cache: true removes a whole class of redundant network traffic.
Default templates are text-only. Almost every real UI needs richer rows:
templates: {
suggestion: function (d) {
return '<div class="tt-row"><img src="' + d.avatar + '"><strong>' + d.name + '</strong><br>' + d.email + '</div>';
},
notFound: '<div class="tt-empty">No matches</div>',
footer: '<div class="tt-footer">Press Enter to search</div>'
}
Use empty and pending templates too; they are the difference between "broken" and "loading".
Typeahead emits jQuery events on the input. The useful ones:
typeahead:select – user picked a suggestion. Fire your "navigate" or "apply filter" logic here.typeahead:autocomplete – user accepted the inline hint (Tab / Right Arrow). Often you want the same handler as select.typeahead:asyncrequest / asyncreceive / asyncsuccess / asyncerror – wrap in a loading spinner.typeahead:open / typeahead:close – toggle surrounding UI.$('#search').on('typeahead:select typeahead:autocomplete', function (e, datum) {
window.location.href = '/person/' + datum.id;
});
Because Bloodhound is just a suggestion engine, you can use it without the UI:
engine.get(query, cb) for programmatic lookups (e.g., a custom dropdown).engine.initialize() to warm the prefetch cache during idle time.engine.add([...]) to inject entries after a successful create/save.engine.clear() when the underlying data changes and you don't want stale caches.This is often the most useful pattern in day-to-day work: treat Bloodhound as a small cache-and-rank library, and drive your own markup from get().
The generated markup is wrapped in .twitter-typeahead > .tt-hint + .tt-input + .tt-menu. With Bootstrap, drop .tt-* into your input group and restyle .tt-menu to match .dropdown-menu. Set position: relative on the wrapper and make sure nothing in your framework sets input { width: 100% } differently on .tt-input vs .tt-hint – misalignment here is the classic visual bug.
minLength so empty focus doesn't fire requests.notFound text and a typeahead:asyncerror handler that falls back to a plain submit.display vs displayKey0.10 uses display; 0.11 uses displayKey. If results appear in the menu but the input stays blank after selecting one, you are on 0.11 with display. This is the #1 reported issue on every tutorial that copied 0.10.
datumTokenizer and queryTokenizer are required on Bloodhound. For object arrays you need Bloodhound.tokenizers.obj.whitespace('field'), not flat whitespace. Wrong tokenizer = no matches, but no error, which makes it feel like the data isn't loading. Log engine.get('some query', console.log) to confirm.
Without minLength and limit, every keystroke triggers an AJAX call and renders everything returned. Also use rateLimitWait / rateLimitBy on Bloodhound (wait ms) to debounce. If your backend isn't idempotent/fast, you'll see out-of-order results – Bloodhound cancels in-flight requests, but a slow server can still surface stale suggestions if sufficient isn't set.
sufficient – for remote datasets returning partial results, add sufficient: function (q, result) { return result.length >= 5; } so Bloodhound stops asking for more data once it has enough. Forgetting this causes "it keeps hitting my API even when results are shown."
Typeahead.js requires jQuery. If you're building a modern JIT/Vite/Turbo app, this is the main adoption cost – load jQuery globally or use one of the community forks (e.g., corejs-typeahead), which is the practical path when the original repo is archived.
twitter/typeahead.js is no longer maintained upstream (last release 0.11.1). Check actively maintained forks/alternatives before betting a new project on it. If you inherit it, treat the bundled .js as pinned and avoid patching.
.tt-hint misalignmentThe hint input is an overlay. Any padding, font-size, or border difference between .tt-input and .tt-hint shifts the ghost text. Fix by making both inherit a shared class rather than duplicating styles.
typeahead:select vs native input eventsWhen a suggestion is chosen, the input's change event fires after typeahead's select, and the value may be the display string, not your id. Keep the selected datum in a hidden input inside your select handler – don't try to reverse-map the display string later.
identify and merging resultsidentify defaults to the display string. When you merge datasets or update via engine.add(), provide identify: function (d) { return d.id; } so duplicates are deduped correctly. Without it, the same object from prefetch and remote shows twice.
source returns a function/engine, minLength satisfied, and displayKey matches a real field.templates.suggestion.highlight: true must be on the first argument (the options object), not the dataset.localStorage from a previous schema version – engine.clear() / bump the cache key.engine.clear() and re-initialize(); Bloodhound caches per query.sorter for non-default ranking.matcher when you need fuzzy matching beyond tokenizers.typeahead(null, dsA, dsB)).source as a plain function (query, syncResults) => ... for one-off lists where Bloodhound is overkill.How can I help you explore Laravel packages today?