Components
@searchstack/autocomplete is a tiny, framework-agnostic JavaScript module that turns any HTML <input> into an as-you-type search box wired to one of your lists or groups. It handles debouncing, request cancellation, keyboard navigation, mobile full-screen mode and search history out of the box, and lets you render every row however you like with a template function. It ships as both an npm package and a CDN bundle.
It's an optional convenience for web front-ends, not a dependency. Under the hood it just calls the suggest REST endpoint — a plain HTTPS request you can make from any language or platform. If you're not building a web page, skip the module and call suggest directly.
Try it
An interactive preview wired to a live movies list on the public API — start typing a film (try “matrix”, “inception” or “dark”) to see the dropdown, keyboard navigation and custom row template in action:
This box is live: it calls the suggest endpoint on each keystroke using a read-only demo key, and draws each row with a custom template.
Install
From npm:
npm install @searchstack/autocomplete
…or straight from the CDN, no build step:
import { attachList, attachGroup } from 'https://cdn.jsdelivr.net/npm/@searchstack/autocomplete@2/dist/searchstack-autocomplete.js'
Authentication
The component calls the public API on the visitor’s behalf, so every call is authenticated. Pass your API key as the second argument to attachList / attachGroup — it’s required and sent as X-API-Key.
Because the key ships in your page’s JavaScript, use a list-scoped, read-only key — the safest key for a browser. When you create the key, choose A single list (or A single group) as its scope and give it read-only (search-result:read) permission. A list-scoped key can only ever search that one list: if it’s copied out of your page source it grants nothing else — no other list, no account settings, no writes — and tampering with the list name in the URL just returns 403. To authenticate a signed-in user instead, pass their access_token in the options (sent as Authorization: Bearer).
Usage
There are two entry points — attachList for a single list and attachGroup for a group of lists. Both take the same arguments: the id of an <input>, an API key scoped to suggest, your account name, the list or group name, and the version. For a group, pass 'latest' to always track the group’s current membership version (so it never rots when the group is bumped), or a concrete integer to pin a frozen version.
<input id="txt1" />
<script type="module">
import { attachList, attachGroup } from 'https://cdn.jsdelivr.net/npm/@searchstack/autocomplete@2/dist/searchstack-autocomplete.js'
// Target a List version:
await attachList('txt1', 'YOUR_SUGGEST_KEY', 'my-account', 'my-list', 1);
// ...or a Group, always tracking its current membership version:
await attachGroup('txt1', 'YOUR_SUGGEST_KEY', 'my-account', 'my-group', 'latest');
</script>
Custom row templates
By default each suggestion renders its name. Pass a template — a function that receives the record and returns an HTML string — to control the row. When targeting a group you can branch on data.list_name to draw a different shape per source list — for example, over a movies-and-actors group:
await attachGroup('movies-actors-txt', 'YOUR_SUGGEST_KEY', 'my-account', 'movies-and-actors', 3, {
template: (data) => {
if (data.list_name === 'actors') {
return `<div style="display:flex;gap:10px">
<img src="${data.image_url}" style="width:50px" />
<div><strong>${data.name}</strong><br><small>Actor</small></div>
</div>`;
}
return `<div style="display:flex;gap:10px">
<img src="${data.poster_url}" style="width:50px" />
<div><strong>${data.name}</strong> <small>(${data.year})</small><br><small>${data.genre}</small></div>
</div>`;
}
});
Options
Every option is passed in the sixth argument to attachList / attachGroup. All are optional.
| Option | Default | What it does |
|---|---|---|
api_key | — | API key sent as X-API-Key. Supply this or access_token. |
access_token | — | JWT sent as Authorization: Bearer. Supply this or api_key. |
base_url | public API | Override the API base URL (e.g. a regional or self-hosted endpoint). |
delay | 200 | Milliseconds to wait after a keypress before calling the API (debounce). |
minimum_characters | 2 | Don’t call the API until at least this many characters are typed. |
suggestion_options | — | Extra suggest parameters passed through to the API: size, filter, radius, skip, cache. |
template | name only | Function (data) => htmlString that renders each row (used on desktop and mobile). |
headless | false | Fetch and dispatch suggestions (events / callbacks) without rendering the list. |
history_template | template | Template for previously-selected (history) rows. |
allow_multiple | true | When false, attaching first destroys any previously attached instances. |
footer_template | — | Function returning HTML appended below the list. |
enable_history | true | Remember and prioritise a user’s recent selections. |
enable_repositioning | false | Let the list flip to the best fit (right / top / left) when it doesn’t fit below the input. |
full_screen_on_mobile | true | Open the list full-screen on mobile devices. |
mobile_max_screen_width | 500 | Screen width (px) at or below which mobile behaviour kicks in. |
full_length | true | Make the list the same width as the input. |
list_style / list_item_style / history_item_style | — | Inline CSS injected into the list / each row / each history row (see also CSS variables). |
selected, suggested, search | — | Event-handler functions (see events below); the same signals fire as DOM events too. |
selected_failed, suggested_failed | — | Handlers receiving a status and message when a call fails. |
Events
Alongside the handler options, the component dispatches DOM events on document so any part of your page can react:
| Event | Fires when… | Payload |
|---|---|---|
searchstack-suggestions | suggestions come back | e.data, e.query |
searchstack-search | the user submits a search | e.query |
searchstack-selected | a suggestion is chosen | e.data, e.id, e.query |
searchstack-suggestions-failed | a suggest call fails | e.status, e.message, e.query |
searchstack-selected-failed | resolving a selection fails | e.status, e.message, e.id |
document.addEventListener('searchstack-selected', (e) => {
console.log(e.id, e.data); // navigate to the chosen record, etc.
});
Theming with CSS variables
Override these custom properties on the input’s container (or :root) to restyle the dropdown without touching the component’s markup:
| Variable | Default | Controls |
|---|---|---|
--searchstack-list-background-color | #fff | Dropdown background. |
--searchstack-list-font-size | 0.9em | Suggestion text size. |
--searchstack-list-item-background-hover-color | — | Row background on hover. |
--searchstack-list-item-background-focused-color | — | Row background on keyboard focus. |
--searchstack-list-*-margin-* | varies | Fine margins around the list on each edge (top / right / bottom / left). |
The list exposes a full family of per-edge margin variables (--searchstack-list-top-margin-left, …) for pixel-level positioning; set only the ones you need.
Full-screen on mobile
With full_screen_on_mobile: true (the default), the suggestion list opens full-screen on phones for a native-app feel — set it to false to keep the inline dropdown everywhere.
await attachList('txt1', 'YOUR_SUGGEST_KEY', 'my-account', 'my-list', 1, { full_screen_on_mobile: true });