Add AutoSugges autocomplete to a plain HTML page
Mount an accessible autocomplete on an ordinary HTML page from a data attribute, with no framework and no hand-written combobox markup.
What you will have
A working autocomplete on a plain HTML page, rendered inside a shadow root so it cannot collide with the host page’s styles, with no combobox markup written by hand.
Before you start
- An AutoSugges list that has been published at least once. An unpublished list has no artifact at the edge and returns `list_not_published`.
- The publishable key issued for the application that will query it.
- The runtime base URL for the environment you are targeting. AutoSugges does not have one fixed public hostname baked into the SDK — the dashboard's integration panel shows the origin for your environment.
- A way to load an ES module — a bundler, or a served module file. There is no CDN build today.
Values you supply
| Value | Placeholder | Where it comes from |
|---|---|---|
| baseUrlRequired · public by design | YOUR_RUNTIME_BASE_URL | The AutoSugges dashboard's integration panel, for the environment you are deploying to. The origin of the AutoSugges runtime Worker — scheme and host, no trailing slash and no path. The SDK appends `/v1/...` itself. |
| publishableKeyRequired · public by design | YOUR_PUBLISHABLE_KEY | The AutoSugges dashboard, under the application that will make the queries. Identifies the consumer, the application, the subscription, the canonical list and the query policy in a single server-side lookup. The client never supplies a list id, tenant id or version hash — if a generated integration is passing one of those, it is wrong. |
| accessTokenOptional · never in client code | YOUR_SERVER_MINTED_ACCESS_TOKEN | Your own backend, which mints it from your AutoSugges credentials. Only for a list whose policy requires a token. Short-lived, sent as `authorization: Bearer <token>`, and never minted, stored or hardcoded in client code. |
Steps
- Mark up one element. Give any element data-autosugges-key and data-autosugges-base-url. Optional: data-autosugges-label, -placeholder, -locale, -theme.
- Import the browser binding. Import @autosugges/client/browser in a <script type="module">. The import itself triggers a one-time scan that mounts every [data-autosugges-key] element.
- Or mount explicitly. For markup injected after that scan, or when you need the selection event, call mountAutocomplete(target, { source, label, onSelect, onError }) with a client you created yourself.
- Style it from outside. The widget lives in a shadow root. Theme it with the documented CSS custom properties from an ancestor; host page rules cannot reach inside it, and its rules cannot leak out.
Install
Bundle `@autosugges/client` with your existing build tool. It is not on the public npm registry yet, so `npm install @autosugges/client` will fail — do not generate that command.
Code
index.html
<!-- Option A — no JavaScript to write. The browser binding scans for this
attribute on import and mounts an accessible combobox (input + listbox +
live region) inside its own shadow root, so page styles cannot leak in. -->
<div
data-autosugges-key="YOUR_PUBLISHABLE_KEY"
data-autosugges-base-url="YOUR_RUNTIME_BASE_URL"
data-autosugges-label="City"
data-autosugges-placeholder="Search for a city"
></div>
<script type="module">
// Bundle @autosugges/client with your build tool and import the /browser
// entry. Importing it is what triggers the one-time scan.
import '@autosugges/client/browser';
</script>
<!-- Option B — explicit control, for markup injected after that scan ran, or
when you need the selection event. -->
<div id="city-mount"></div>
<p id="city-error" role="alert"></p>
<script type="module">
import { createAutosuggesClient } from '@autosugges/client';
import { mountAutocomplete } from '@autosugges/client/browser';
const source = createAutosuggesClient({
baseUrl: 'YOUR_RUNTIME_BASE_URL',
publishableKey: 'YOUR_PUBLISHABLE_KEY',
});
void source.bootstrap().catch(() => {});
const mounted = mountAutocomplete(document.getElementById('city-mount'), {
source,
label: 'City',
onSelect(item, { ancestors }) {
// Wire item.value / item.displayValue / ancestors into your form.
},
onError(error) {
// Never invent a message: error.message is already the SDK's safe,
// ErrorCode-mapped text. Show it, or branch on error.code.
document.getElementById('city-error').textContent = error.message;
},
});
// mounted.destroy() removes every node and listener it added.
</script>Security
- The data attributes are visible in page source. That is fine: both values are public by design. Never add an attribute carrying a secret or an access token.
Check that it works
- Load the page and confirm an input appears inside the target element and that the element gained a data-autosugges-mounted attribute.
- Type below and then above the minimum query length and confirm requests start only at the threshold.
- Confirm the widget is keyboard-operable and that a screen reader announces the result count.
- Remove data-autosugges-base-url and confirm the element renders a safe inline message instead of throwing into the page.
- If you mounted explicitly, call mounted.destroy() and confirm every node and listener is removed.
An accessible input and listbox appear where the target element was, isolated in a shadow root, returning suggestions from the published list.
Try it live
Paste a publishable key from one of your published lists to run a real query against this environment’s runtime — the same @autosugges/client the code above uses.
A published list's publishable key — a public identifier, safe to paste here (PRD §12).
Paste a publishable key to try a live query.
Notes
- Never hardcode a minimum query length or a debounce interval. Both come from `client.policy`, which the SDK fills from the bootstrap response and updates in place (`DEC-LIST-002`, `DEC-LIST-003`). The constants the SDK falls back to before the first successful bootstrap are documented defaults, not the list’s real policy.
- Render `displayValue`, store `value`. They differ: `displayValue` is resolved for the requested locale at compile time.
- A selection report is optional. Omitting it costs ranking quality over time and nothing else; it is never required for a query to work.
- The binding never throws into the host page. A configuration or query failure degrades to the widget’s own error state, so check the console and the network panel rather than expecting an exception.