npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@magicx-eng/ai-autocomplete-react

v0.1.43

Published

AI Autocomplete React SDK — guided autocomplete with pill-based input and dropdown suggestions

Readme

@magicx-eng/ai-autocomplete-react

A React/TypeScript SDK that provides a guided AI-powered autocomplete experience with pill-based input and dropdown suggestions. Powered by @magicx-eng/ai-autocomplete-vanilla under the hood.

Features

  • Two tiers of integration — use the full <AIAutocomplete /> component or go headless with useAIAutocomplete() + <AIAutocompleteDropdown />
  • Rich inline input (Tier 1) — a single contentEditable surface: typed text, bold completed params, and inline pills share one editing context
  • Pill-based input — non-editable inline pills for unfilled parameters, bold inline text for completed ones
  • Instant exact-match bolding — typing the full text of an option immediately promotes it to a completed param (no debounced fetch wait). Works in the normal typing flow and while re-editing an existing completed param.
  • Re-edit completed params — tap a bold completed param to replace it; the dropdown re-opens with the cached options the server originally returned for that param
  • Pill placement — render pills inline in the input or inside the dropdown
  • Light/dark mode — built-in themes with prefers-color-scheme support, fully overridable via CSS variables
  • Inherits your font — defaults to the host page's font, with --aia-font-family to pin a specific font on the library
  • Access token auth — short-lived tokens with automatic refresh, single-flight deduplication, and 401 retry
  • Keyboard navigation — arrow keys, enter to submit, tab to autocomplete, backspace to un-bold the last completed param
  • IME-safe — composition events are buffered so input text is committed once, after composition ends
  • Client-side filtering — instant substring filtering on every keystroke
  • Option overrides — inject or dynamically generate client-side options per suggestion type
  • Controlled & uncontrolled — works out of the box or integrates with external state
  • Ref forwarding — imperative focus(), blur(), reset(), and setMode() via ref
  • Accessible — ARIA combobox 1.2 pattern with role="listbox", aria-activedescendant
  • Animations — option selection streak animation, text shimmer on newly added params
  • Loading skeleton — while a fetch is in flight, the dropdown and inline pills keep the previous layout (same count and widths) with their text masked and a shimmer pulse. The skeleton is held back until the selection streak animation finishes, so taps don't visually "stutter" into loading.
  • Lightweight — styles auto-injected at runtime
  • TypeScript first — full type definitions shipped with the package

Installation

pnpm add @magicx-eng/ai-autocomplete-react

Peer Dependencies

React 17 or later:

pnpm add react react-dom

Two Tiers

| Tier | What you get | What you own | Use when | |---|---|---|---| | Tier 1: Full | <AIAutocomplete /> — input, dropdown, pills, state | Nothing — drop in and go | You want a complete widget with zero setup | | Tier 2: Headless | useAIAutocomplete() — state, actions, spread props | The input and layout JSX | You need a custom input or want full control over rendering |


Tier 1: Full Component

Drop-in component that owns the input, pills, dropdown, and all state:

import { AIAutocomplete } from "@magicx-eng/ai-autocomplete-react";

function App() {
  return (
    <AIAutocomplete
      apiConfig={{ endpoint: "https://api.example.com/ac/suggest", apiKey: "your_api_key" }}
      onSubmit={(result) => {
        console.log(result.query);            // "Create a email"
        console.log(result.raw_query);        // "Create a {{TASK_1}}"
        console.log(result.completed_params); // [{ placeholder: "{{TASK_1}}", type: "task", ... }]
      }}
      className="my-autocomplete"
    />
  );
}

Controlled Mode

const [text, setText] = useState("");
const [params, setParams] = useState([]);

<AIAutocomplete
  value={text}
  onChange={setText}
  completedParams={params}
  onParamsChange={setParams}
  onSubmit={(result) => console.log(result)}
/>

Imperative Handle

import { useRef } from "react";
import { AIAutocomplete, type AIAutocompleteHandle } from "@magicx-eng/ai-autocomplete-react";

const ref = useRef<AIAutocompleteHandle>(null);
// ref.current?.focus()
// ref.current?.blur()
// ref.current?.reset()
// ref.current?.setMode("dark")

<AIAutocomplete ref={ref} onSubmit={handleSubmit} />

Focus Control

The component auto-focuses the contentEditable editor on mount. To opt out, pass autoFocus={false}. Listen to focus changes with onFocus / onBlur:

<AIAutocomplete
  autoFocus={false}
  onFocus={() => setIsActive(true)}
  onBlur={() => setIsActive(false)}
  onSubmit={handleSubmit}
/>

Backspace into a completed param

Pressing Backspace while the caret is inside or immediately after a bold completed param drops the param's "completed" status and removes one grapheme before the caret. The remaining text stays in the editor as plain (un-bold) text so the user can keep editing instead of losing the whole phrase.

Re-edit a completed param

Tapping a bold completed param enters re-edit mode — the dropdown re-opens with the cached options the server originally returned for that param. From there:

  • Typing atomically replaces the bold with what you type. If what you type exactly matches one of the cached options, it's re-promoted to a bold completed param immediately.
  • Clicking an option replaces the bold with the new selection.
  • Arrow keys, Escape, or clicking outside the param exit re-edit mode without changing anything.

After a completed param is added (by any means — option click, exact-match typing, or re-edit), the caret always lands right after the trailing space following the bold so typing can continue immediately. A space is inserted if one wasn't already there.


Tier 2: Headless

Use the hook and dropdown separately for full control over the input and rendering:

import { useAIAutocomplete, AIAutocompleteDropdown } from "@magicx-eng/ai-autocomplete-react";

function App() {
  const {
    completedParams,
    suggestionPills,
    segments,
    inputProps,
    dropdownProps,
    isLoading,
    error,
    reset,
  } = useAIAutocomplete({
    onSubmit: (result) => {
      handleMySubmit(result);
      reset(); // start a new session
    },
    apiConfig: { endpoint: "https://api.example.com/ac/suggest", apiKey: "your_api_key" },
  });

  return (
    <div>
      <textarea {...inputProps} />
      <AIAutocompleteDropdown {...dropdownProps} />
    </div>
  );
}

Always call reset() after handling submit. It clears the input and rotates the per-session session_id. This applies whether the submit was triggered by Enter, a custom button, or any other mechanism.


API Reference

<AIAutocomplete />

| Prop | Type | Default | Description | |---|---|---|---| | onSubmit | (result: AutocompleteResult) => void | required | Called on Enter or submit button. | | onError? | (error: Error) => void | — | Called when a fetch fails. | | apiConfig? | APIConfig | — | Runtime API configuration (see below). | | optionOverrides? | Record<string, (query: string) => SuggestionOption[]> | — | Override options per suggestion type. | | maskCompletedText? | boolean | false | When true, omits completed params' literal text from API requests (for masking PII/sensitive values from the server). | | className? | string | — | CSS class applied to the container. | | columns? | number | 2 | Number of columns in the dropdown grid. | | pillPlacement? | "inline" \| "dropdown" \| "hidden" | "inline" | Where to render unfilled pills. "hidden" hides pills entirely. | | mode? | "light" \| "dark" \| "auto" | "auto" | Color mode. "auto" follows prefers-color-scheme. | | optionsPosition? | "above" \| "below" | "below" | Where the dropdown opens relative to the input. | | animations? | boolean | true | Enable/disable all SDK animations (streak + shimmer). | | dropdownTrigger? | "auto" \| "manual" \| "hidden" | "auto" | When the dropdown appears. "auto" = when options available. "manual" = only on pill tap, closes after selection. "hidden" = never shows. | | closeDropdownOnBlur? | boolean | true | When true, the dropdown closes if the input loses focus. Set to false to keep it open whenever options are available, regardless of focus. | | showNonTappableOptions? | boolean | true | When true, non-tappable options are rendered alongside tappable ones in the dropdown. Set to false to hide non-tappable options entirely. | | autoFocus? | boolean | true | Focus the input on mount. Set to false to leave focus to the consumer. | | onFocus? | () => void | — | Called when the input gains focus. | | onBlur? | () => void | — | Called when the input loses focus. | | value? | string | — | Controlled text value. | | completedParams? | CompletedParamState[] | — | Controlled completed params. | | onChange? | (value: string) => void | — | Called when text changes (controlled mode). | | onParamsChange? | (params: CompletedParamState[]) => void | — | Called when params change (controlled mode). | | submitButton? | ReactNode | — | Custom submit button. Pass any ReactNode to replace the default arrow button. Pass null to render no button. Clicks bubble up and trigger submit, so consumer-supplied buttons work without re-wiring onClick. | | ref? | Ref<AIAutocompleteHandle> | — | Imperative handle with focus(), blur(), reset(), and setMode(). |

Custom submit button

<AIAutocomplete
  onSubmit={handleSubmit}
  submitButton={<button className="my-button">Go</button>}
/>

// Or hide the button entirely:
<AIAutocomplete onSubmit={handleSubmit} submitButton={null} />

APIConfig

A discriminated union: APIKeyConfig | AccessTokenConfig.

API Key Mode (default)

{ apiKey: "your_api_key", authScheme: "Bearer", endpoint: "/ac/suggest" }

| Field | Type | Description | |---|---|---| | type? | "apiKey" | Optional discriminator. Default when omitted. | | apiKey? | string | API key for Authorization header. | | authScheme? | "Bearer" \| "Basic" | Auth header scheme. Default: "Bearer". | | endpoint? | string | Full URL for the suggest endpoint. Default: "https://api.ai-autocomplete.com/api/suggest". | | appIdentifier? | string | Value for the X-App-Identifier header. | | headers? | Record<string, string> | Additional headers merged into every request. |

Access Token Mode

<AIAutocomplete
  apiConfig={{
    type: "accessToken",
    getAccessToken: async () => {
      const res = await fetch("/api/ac-token");
      const { access_token, expires_at } = await res.json();
      return { accessToken: access_token, expiresAt: expires_at };
    },
  }}
  onSubmit={handleSubmit}
/>

| Field | Type | Description | |---|---|---| | type | "accessToken" | Required discriminator. | | getAccessToken | () => Promise<AccessTokenResult> | Required. Called when the SDK needs a token. | | accessToken? | string | Initial token. Avoids one round-trip on mount. | | endpoint? | string | Suggest endpoint URL. Default: "https://api.ai-autocomplete.com/api/suggest". | | appIdentifier? | string | Value for the X-App-Identifier header. | | headers? | Record<string, string> | Additional headers merged into every request. |

The SDK handles token refresh transparently: 401 → getAccessToken → retry (once). Concurrent 401s share a single refresh. Tokens refresh proactively 30s before expiresAt.

useAIAutocomplete(options)

The headless hook for Tier 2. Accepts the same props as <AIAutocomplete /> except for the rendering-only ones: className, ref, pillPlacement, mode, optionsPosition, animations, and autoFocus (those belong to the wrapping component — the hook doesn't own the input element). onFocus and onBlur are forwarded and fire whenever the consumer-owned textarea's focus changes (they're driven by inputProps.onFocus / inputProps.onBlur).

Return Value

State

| Field | Type | Description | |---|---|---| | completedParams | CompletedParamState[] | Filled parameters. | | suggestionPills | Suggestion[] | Unfilled suggestions (pills). First item is the active pill. | | segments | Segment[] | Input text split into typed text vs completed params — completed segments render as bold <strong> runs inside the editor. | | newParamId | string \| null | ID of the most recently added param (for shimmer animation). | | suggestions | Suggestion[] | All suggestions from server (including placeholder type). | | activeIndex | number | Highlighted option index. -1 = none. | | isLoading | boolean | True when the dropdown should render its loading skeleton — a fetch is in flight, no option-selection animation is playing, and the user is not in re-edit mode (where cached options stay visible). | | isReady | boolean | Server indicates query is complete. | | error | Error \| null | Last fetch error. |

Actions

| Field | Type | Description | |---|---|---| | setActivePill | (index: number) => void | Move pill at index to front (active). | | removeLastParam | () => void | Remove the last completed param from state. The text stays in the input as plain text. | | clearNewParamId | () => void | Clear shimmer animation state. | | reset | () => void | Clear all state, re-fetch, and start a new session (rotates session_id). Call this after handling submit. |

Spread Props

| Field | Type | Description | |---|---|---| | inputProps | object | Spread onto a <textarea>. Includes value, placeholder, onChange, onKeyDown, and ARIA attributes. | | dropdownProps | AIAutocompleteDropdownProps | Spread onto <AIAutocompleteDropdown />. Includes options, highlight, selection, pills, and open state. |

<AIAutocompleteDropdown />

The dropdown component for Tier 2. Spread dropdownProps from the hook.

| Prop | Type | Description | |---|---|---| | suggestions | Suggestion[] | Suggestions to display. | | activeIndex | number | Highlighted option index. | | onSelect | (option: SuggestionOption) => void | Called when an option is selected. | | onHighlight | (index: number) => void | Called on mouse hover. | | isOpen | boolean | Whether the dropdown is visible. | | id | string | Listbox ID for ARIA. | | className? | string | CSS class applied to the dropdown. | | pills? | Suggestion[] | Pills to render inside the dropdown. | | onPillClick? | (index: number) => void | Called when a pill is clicked. | | showPills? | boolean | Whether to render pills. Default: true. | | isLoading? | boolean | When true, the dropdown renders its pills + options as a skeleton: text masked, layout (count and widths) preserved, shimmer pulse animating. Falls back to a generic 3-bar placeholder when no pills/options are cached. |

AutocompleteResult

| Field | Type | Description | |---|---|---| | query | string | Plain text as the user sees it. | | raw_query | string | Text with placeholder tokens (e.g. "Create a {{TASK_1}}"). | | completed_params | CompletedParam[] | Filled parameter values. |


CSS Customization

Styles are auto-injected at runtime — no CSS import needed. Built-in light and dark defaults apply automatically based on mode.

CSS Variables

Override on the container (via className). All defaults use :where() (zero specificity) — your overrides always win.

| Variable | Light | Dark | Description | |---|---|---|---| | --aia-font-family | inherit | inherit | Font used by the library. Defaults to inherit so the library picks up your page's font automatically. Set this to pin a specific font on the library without changing the surrounding page. | | --aia-pill-bg | #bdbdbd | #bdbdbd | Pill background | | --aia-pill-color | #000000 | #ffffff | Pill text | | --aia-pill-font-size | 19px | 19px | Pill font size | | --aia-option-bg | transparent | transparent | Highlighted option background | | --aia-option-color | #000000 | #ffffff | Option text | | --aia-option-color-selected | #000000 | #ffffff | Highlighted option text | | --aia-option-font-size | 19px | 19px | Option font size | | --aia-written-text-color | #000000 | #ffffff | Input text | | --aia-written-text-font-size | 19px | 19px | Input text font size | | --aia-caret-color | --aia-written-text-color | --aia-written-text-color | Editor caret color. Override independently of input text color. | | --aia-submit-bg | #000000 | #ffffff | Submit button background | | --aia-submit-color | #ffffff | #000000 | Submit button icon color | | --aia-dropdown-bg | — | — | Optional bg color the dropdown's "glass" rim shadow tints toward. Set this to the page background behind the dropdown so the bottom-corner glow blends seamlessly. | | --aia-scrollbar-thumb | rgba(0, 0, 0, 0.3) | rgba(0, 0, 0, 0.3) | Color of the option list's scrollbar thumb (Firefox + WebKit). | | --aia-streak-rgb | 99, 102, 241 | 255, 255, 255 | Comma-separated RGB triplet used to tint the option-selection streak animation (consumed via rgba(var(--aia-streak-rgb), …)). | | --aia-streak-glass-bg | rgba(99, 102, 241, 0.1) | rgba(255, 255, 255, 0.1) | Background fill for the streak's glass-pill effect. | | --aia-skeleton-bg | rgba(189, 189, 189, 0.51) | #333539 | Fill color for the loading skeleton bars and the masked text in cached pills/options. |

Legacy --aia-color-* variables are still supported as fallbacks.

Per-mode Overrides

.my-autocomplete[data-mode="light"] {
  --aia-pill-bg: #e2e8f0;
}
.my-autocomplete[data-mode="dark"] {
  --aia-pill-bg: #334155;
}

Selector Hooks

For styling beyond the CSS variables, target these stable data-aia-* attributes (CSS-module class hashes are not part of the public API):

| Attribute | Element | |---|---| | [data-aia-editor] | Editor area wrapping the contentEditable + inline pill list | | [data-aia-input] | The Tier 1 contentEditable <div> that owns typed text and bold completed params. (Replaces the previous [data-aia-textarea] selector.) | | [data-aia-pill-list-container] | Inline sibling of the editor that holds unfilled-suggestion pills | | [data-aia-submit] | Submit button | | [data-aia-pill] | Each unfilled-suggestion pill | | [data-aia-pillbar] | Pill bar container inside the dropdown | | [data-aia-option] | Each suggestion option | | [data-aia-dropdown] | The dropdown root (listbox) |

Completed params render as inline <strong> elements inside the editor. Override their weight with [data-aia-input] strong { font-weight: 700; } (the built-in style uses :where() so any consumer selector wins without !important).

/* Solid (non-glass) dropdown */
.my-autocomplete [data-aia-dropdown] {
  background: #fff;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
  backdrop-filter: none;
}

Sessions

Every /api/suggest request carries a meta.session_id UUID. A session runs from mount (or the last reset()) until the next reset(). All requests in one session share the same session_id; calling reset() starts a new one.

The contract is simple: after the user submits the query, call reset(). That clears the input and rotates session_id so the next session begins clean.

Why it matters: the server uses session_id to track each session's history — what the user has been typing sequentially, which options they've selected, and how the query evolved. That context lets the model produce better, more relevant suggestions on subsequent requests within the same session. Calling reset() at the right moment (when the user actually submits) keeps that history accurate, so your users get higher-quality results.

  • Tier 1 <AIAutocomplete /> does this automatically — it calls reset() for you after onSubmit returns, for both Enter-key and built-in-button submits.
  • Tier 2 useAIAutocomplete() — you own the submit flow, so call reset() from your onSubmit handler (see the example above) or from your custom button after firing onSubmit.

Option Overrides

<AIAutocomplete
  optionOverrides={{
    account: () => [
      { text: "Savings", is_tappable: true, kind: null },
      { text: "Checking", is_tappable: true, kind: null },
    ],
    value: (query) => {
      const digits = query.replace(/\D/g, "");
      if (!digits) return [{ text: "$100", is_tappable: true, kind: null }];
      return [{ text: `$${digits}`, is_tappable: true, kind: null }];
    },
  }}
  onSubmit={handleSubmit}
/>

License

Private package. All rights reserved.