# Integration Tiers | JavaScript AI Autocomplete Docs

> For the complete documentation index, see [llms.txt](https://ai-autocomplete.com/llms.txt). A markdown version of any page on this site is available by appending `.md` to its URL path — the homepage is at `/index.md`.

Integration Tiers guide for integrating the AI Autocomplete SDK with JavaScript. Code examples, setup, and API reference.

Source: https://ai-autocomplete.com/docs/vanilla/integration-tiers

---

Three tiers, from drop-in to headless. Pick the one that matches how much DOM control you want.

Tier 1

### Full component

Drop-in. Library creates input + dropdown + pills + keyboard + state.

**Use when** you want a complete widget with zero setup.

Tier 2

### Dropdown only

Library renders dropdown + pills. You own the input element.

**Use when** you need a custom input (rich text, search bar, etc).

Tier 3

### Headless

State + controllers + derived data. You render everything.

**Use when** you're building a framework wrapper or need full DOM control.

## Tier 1 — full component

The library creates and owns the input, dropdown, pills, and all DOM. Just pass a container.

**full.ts**

```tsx
import { AIAutocomplete } from "@magicx-eng/ai-autocomplete-vanilla";

const ac = new AIAutocomplete(container, {
  renderMode: "full", // default
  apiConfig: { apiKey: "pk_v1_your_public_key" },
  columns: 2,
  mode: "auto",            // "light" | "dark" | "auto"
  pillPlacement: "inline", // "inline" | "dropdown" | "hidden"
  onSubmit: (result) => console.log(result),
  onError: (err) => console.error(err),
});

// Imperative methods
ac.focus();
ac.reset();
ac.setMode("dark");
ac.update({ animations: false, optionsPosition: "above" });
ac.destroy();
```

## Tier 2 — dropdown only

You own the input element. The library renders the dropdown (with pills) inside a container you provide, and you wire your input's events into the library.

**dropdown-only.ts**

```tsx
import { AIAutocomplete } from "@magicx-eng/ai-autocomplete-vanilla";

const input = document.getElementById("my-input") as HTMLTextAreaElement;
const dropdownContainer = document.getElementById("my-dropdown")!;

const ac = new AIAutocomplete(dropdownContainer, {
  renderMode: "dropdown",
  apiConfig: { apiKey: "pk_v1_your_public_key" },
  onChange: (text) => {
    if (input.value !== text) input.value = text;
  },
});

const handleSubmit = () => {
  sendToBackend(input.value);
  ac.reset(); // start a new session
};

// Wire your input → library
input.addEventListener("input", () => ac.handleTextChange(input.value));
input.addEventListener("keydown", (e) => ac.handleKeyDown(e));
submitButton.addEventListener("click", handleSubmit);
```

Pills always render inside the dropdown in this tier. You don't need to render them separately.

Always call ac.reset() after submit

Tier 1 auto-resets after the built-in submit. In Tier 2 the consumer owns the input element and the submit flow — don't pass onSubmit to new AIAutocomplete(). Wire up submit however you like (a button click, a form submit, your own Enter handler) and call ac.reset() at the end of that handler. It clears the input and rotates the per-session session_id so the server's history stays accurate; skipping it degrades suggestion quality on subsequent submissions.

## Tier 3 — headless

No DOM at all. You get state, actions, and derived data. You render everything yourself. This is what framework wrappers (React, Vue, Svelte) use under the hood.

**headless.ts**

```tsx
import { AIAutocomplete } from "@magicx-eng/ai-autocomplete-vanilla";

const ac = new AIAutocomplete(document.createElement("div"), {
  renderMode: "headless",
  apiConfig: { apiKey: "pk_v1_your_public_key" },
});

const handleSubmit = () => {
  const state = ac.getState();
  sendToBackend(state.text, state.completedParams);
  ac.reset(); // start a new session
};

// Read state synchronously
const state = ac.getState();

// Subscribe to derived state
const unsubscribe = ac.subscribe((state) => {
  renderMyInput(state.text, state.placeholderText);
  renderMyPills(state.actionableSuggestions);
  renderMyDropdown(
    state.filteredOptions,
    state.activeDropdownIndex,
    state.isDropdownOpen,
  );
});

// Forward user actions
myInput.addEventListener("input", () => ac.handleTextChange(myInput.value));
myInput.addEventListener("keydown", (e) => ac.handleKeyDown(e));
myOptionEl.addEventListener("click", () => ac.selectOption(option));
myPillEl.addEventListener("click", () => ac.setActivePill(index));
mySubmitButton.addEventListener("click", handleSubmit);

// Cleanup
unsubscribe();
ac.destroy();
```

Custom & rich-text inputs

The wiring above assumes a <textarea> or <input>, whose caret the library reads directly. For a contentEditable or rich-text editor, also call ac.handleCaretMove(offset) on selection changes (with the caret as a plain-text offset) so arrow keys can move from the input into the dropdown.

Always call ac.reset() after submit

Tier 1 auto-resets after the built-in submit. In Tier 3 the consumer owns the entire DOM and the submit flow — don't pass onSubmit to new AIAutocomplete(). Wire up submit however you like and call ac.reset() at the end of that handler. It clears the input and rotates the per-session session_id so the server's history stays accurate; skipping it degrades suggestion quality on subsequent submissions.

Building a framework wrapper

This is exactly how the React SDK is built. Subscribe to state, render your framework's components, forward events. The vanilla core stays unaware of your renderer.
