Loading...

Components

Turn any HTML <input> into an as-you-type search box over one of your lists or groups — type “run”, see “running shoes”.

  • @searchstack/autocomplete — a tiny JavaScript module for any framework, or none. On npm, and prebuilt on a CDN.
  • Built in — waits for a pause in typing, drops out-of-date replies, handles the arrow keys, goes full-screen on phones, remembers recent choices, draws each row from your template.
  • Optional — it only calls the suggest endpoint, which you can call from any language or platform.
1

What you get for two lines of code

This is the module attached to a real list: a dropdown after every keystroke, arrow-key navigation, a grouped heading, and each row drawn by your own template — poster, title, kind and year here. Underneath it is only calling the suggest endpoint, so anything it does you can do from any language.

The autocomplete component showing a dropdown with a custom row template
Try it

Live against a real movies list on the public API, on a read-only demo key and a custom template. Type “matrix”, “inception” or “dark”:

Install

From npm:

npm install @searchstack/autocomplete

…or straight from the CDN, with no build step:

import { attachList, attachGroup } from 'https://cdn.jsdelivr.net/npm/@searchstack/autocomplete@2/dist/searchstack-autocomplete.js'
Authentication
  • Your API key is the required second argument to attachList / attachGroup, sent as X-API-Key.
  • It ships in your page’s JavaScript, so make it list-scoped and read-only: scope A single list (or A single group), permission search-result:read.
  • Such a key grants nothing else — no other list, no account settings, no writes. Changing the list name in the URL returns 403.
  • For a signed-in user instead, pass their access_token in the options (sent as Authorization: Bearer).
Usage

attachList targets one list, attachGroup a group. Both take the id of an <input>, an API key limited to suggest, your account name, the list or group name, and the version — for a group, 'latest' to follow its current version, or a number to pin one.

<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>
Ask
Use 2.4.1 or later. attachAsk and attachAskLauncher arrived in 2.4.0; 2.4.1 fixes Ask on small screens, where the full-screen suggestion list covered the answer panel and swallowed the keys that open it. A missing named import stops the browser loading the whole module, taking attachList and your search box with it — so on a CDN pin @2.4.1, not the @2 range, which jsDelivr can serve stale for hours after a release.

attachAsk is attachList plus an answer: same suggestions, same speed, with an “Ask this” row beneath them that opens a panel over the page, writes the answer out as it is generated, and links back to every section it used.

  • Opening it — click the row, past the last suggestion, end the query in ? (which pre-selects the row, so Enter answers), or Cmd-K. Otherwise Enter is unchanged.
  • In the panel — a trailing ? starts the answer; / move through the cited and related sections.
  • No search box (a home page, a footer) — attachAskLauncher('element-id', { … }) opens the same panel from any button, with no search key. Our own home page uses it.
await attachAsk('txt1', 'YOUR_SUGGEST_KEY', 'my-account', 'my-list', 1, {
  ask_endpoint: '/api/ask',
});
  • Suggestions go straight from the browser to the search API on the key above.
  • The answer does not — it needs a model key, which must never ship in a page. ask_endpoint points at your own backend, which finds the records, calls the model, and streams the result back as server-sent events (sources, then delta, then done).
  • Citations are [^1] markers matching a source’s marker, drawn as links to that section. Strip any marker your own search did not produce.
  • Styling — no branding, follows light and dark mode, restyled with the --searchstack-ask-* variables. Full option list in the package README.
  • These docs run it — the sidebar box is attachAsk. Press Cmd-K.
Custom row templates

Each suggestion shows its name unless you pass a template: a function receiving the record, returning an HTML string. Over a group, branch on data.list_name for a different row per list.

import { fieldText } from 'https://cdn.jsdelivr.net/npm/@searchstack/public-api@4.4.0/dist/index.js';

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="${fieldText(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="${fieldText(data, 'poster_url')}" style="width:50px" />
      <div><strong>${data.name}</strong> <small>(${fieldText(data, 'year')})</small><br><small>${fieldText(data, 'genre') ?? ''}</small></div>
    </div>`;
  }
});
Read fields with fieldText, not ${data.genre}. A facet always arrives as an array, however many values it holds: two genres print as Comedy,Drama, no space, and a one-value field like year only looks fine by accident. fieldText joins values properly, returns a single one as itself, and gives null rather than undefined for a field the record lacks; fieldValues gives the array, fieldNumber a number to compare. All three read a suggestion (flat) and a search hit (nested under fields) alike, and need @searchstack/public-api 4.4.0 or later. Add ?? '' where a row may not carry the field — ${…} prints the word null otherwise.
Options

All optional, all passed in the sixth argument to attachList / attachGroup.

OptionDefaultWhat it does
api_keyAPI key sent as X-API-Key. Supply this or access_token.
access_tokenA signed-in user’s token (JWT) sent as Authorization: Bearer. Supply this or api_key.
base_urlpublic APIA different API address (regional, or self-hosted).
delay200Milliseconds to wait after a keypress before calling the API.
minimum_characters2Don’t call the API until this many characters are typed.
suggestion_optionsExtra suggest settings for the API: size, filter, radius, skip, cache.
templatename onlyFunction (data) => htmlString drawing each row, desktop and mobile.
headlessfalseFetch suggestions and fire the events / callbacks without drawing the list.
history_templatetemplateTemplate for previously-selected (history) rows.
allow_multipletrueWhen false, attaching first removes any previously attached instances.
footer_templateFunction returning HTML added below the list.
enable_historytrueRemember recent selections and show them first.
enable_repositioningfalseLet the list move to the best fit (right / top / left) when it doesn’t fit below the input.
full_screen_on_mobiletrueOpen the list full-screen on mobile devices.
mobile_max_screen_width500Screen width (px) at or below which mobile behaviour starts.
full_lengthtrueMake the list the same width as the input.
list_style / list_item_style / history_item_styleInline CSS for the list / each row / each history row.
selected, suggested, searchEvent-handler functions; the same signals fire as DOM events.
selected_failed, suggested_failedHandlers receiving a status and message when a call fails.
Events

The component also fires DOM events on document:

EventFires when…What it carries
searchstack-suggestionssuggestions come backe.data, e.query
searchstack-searchthe user submits a searche.query
searchstack-selecteda suggestion is chosene.data, e.id, e.query
searchstack-suggestions-faileda suggest call failse.status, e.message, e.query
searchstack-selected-failedlooking up a chosen suggestion failse.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

Set these on the input’s container (or :root) to restyle the dropdown without touching its HTML:

VariableDefaultControls
--searchstack-list-background-color#fffDropdown background.
--searchstack-list-font-size0.9emSuggestion text size.
--searchstack-list-item-background-hover-colorRow background on hover.
--searchstack-list-item-background-focused-colorRow background on keyboard focus.
--searchstack-list-*-margin-*variesFine margins on each edge (top / right / bottom / left).

There is a per-edge margin variable for every edge (--searchstack-list-top-margin-left, …); set only the ones you need.

Full-screen on mobile

The default. Set full_screen_on_mobile: false to keep the dropdown under the box everywhere.

await attachList('txt1', 'YOUR_SUGGEST_KEY', 'my-account', 'my-list', 1, { full_screen_on_mobile: true });
Top