> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify-sam-assistant-widget.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Assistant Widget

> Install and configure the Mintlify Assistant Widget in any website or web application.

export const AssistantWidgetPlayground = ({CodeBlockComponent}) => {
  const EXAMPLE_WIDGET_ID = "YOUR_WIDGET_ID";
  const EMBED_URL = "https://cdn.jsdelivr.net/npm/@mintlify/assistant-widget@0.0/dist/browser/embed.js";
  const PRESENTATION_OPTIONS = [{
    value: "widget",
    label: "Widget"
  }, {
    value: "modal",
    label: "Modal"
  }, {
    value: "panel",
    label: "Panel"
  }];
  const THEME_OPTIONS = [{
    value: "system",
    label: "System"
  }, {
    value: "light",
    label: "Light"
  }, {
    value: "dark",
    label: "Dark"
  }];
  const SIDE_OPTIONS = [{
    value: "top",
    label: "Top"
  }, {
    value: "bottom",
    label: "Bottom"
  }, {
    value: "left",
    label: "Left"
  }, {
    value: "right",
    label: "Right"
  }, {
    value: "inline-start",
    label: "Inline start"
  }, {
    value: "inline-end",
    label: "Inline end"
  }];
  const ALIGN_OPTIONS = [{
    value: "start",
    label: "Start"
  }, {
    value: "center",
    label: "Center"
  }, {
    value: "end",
    label: "End"
  }];
  const INSTALL_OPTIONS = [{
    value: "html",
    label: "HTML"
  }, {
    value: "next",
    label: "Next.js"
  }];
  const [installTarget, setInstallTarget] = useState("html");
  const [variant, setVariant] = useState("widget");
  const [theme, setTheme] = useState("system");
  const [accent, setAccent] = useState("#16a34a");
  const [radius, setRadius] = useState(18);
  const [side, setSide] = useState("bottom");
  const [align, setAlign] = useState("end");
  const [trackEvents, setTrackEvents] = useState(true);
  const [reportErrors, setReportErrors] = useState(true);
  const classNames = (...classes) => classes.filter(Boolean).join(" ");
  const renderSegmentedControl = ({ariaLabel, onChange, options, value}) => <div role="group" aria-label={ariaLabel} className="grid grid-flow-col auto-cols-fr gap-1 rounded-lg bg-gray-100 p-1 dark:bg-white/10">
      {options.map(option => <button key={option.value} type="button" aria-pressed={value === option.value} onClick={() => onChange(option.value)} className={classNames("h-8 rounded-md px-3 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40", value === option.value ? "bg-white text-gray-950 shadow-sm dark:bg-white/15 dark:text-white" : "text-gray-600 hover:text-gray-950 dark:text-gray-400 dark:hover:text-white")}>
          {option.label}
        </button>)}
    </div>;
  const renderSelectField = ({label, onChange, options, value}) => <label className="flex min-w-0 flex-col text-sm font-medium text-gray-700 dark:text-gray-300">
      <span>{label}</span>
      <select value={value} onChange={event => onChange(event.target.value)} className="h-10 w-full rounded-xl border border-gray-950/10 bg-transparent px-3 text-sm font-normal text-gray-950 outline-none transition-shadow focus-visible:ring-2 focus-visible:ring-primary/30 dark:border-white/10 dark:text-white">
        {options.map(option => <option key={option.value} value={option.value}>
            {option.label}
          </option>)}
      </select>
    </label>;
  const renderToggleRow = ({checked, description, label, onChange}) => <label className="flex cursor-pointer items-center justify-between gap-5 py-3">
      <span className="min-w-0">
        <span className="block text-sm font-medium text-gray-950 dark:text-white">
          {label}
        </span>
        <span className="block text-sm text-gray-600 dark:text-gray-400">
          {description}
        </span>
      </span>
      <span className="relative inline-flex shrink-0 rounded-full focus-within:ring-2 focus-within:ring-primary/40">
        <input type="checkbox" role="switch" checked={checked} onChange={event => onChange(event.target.checked)} className="sr-only" />
        <span aria-hidden="true" className={classNames("h-5 w-9 rounded-full transition-colors", checked ? "bg-primary" : "bg-gray-200 dark:bg-white/15")} />
        <span aria-hidden="true" className={classNames("pointer-events-none absolute left-0.5 top-0.5 size-4 rounded-full bg-white shadow-sm transition-all", checked && "ml-4")} />
      </span>
    </label>;
  const appearance = useMemo(() => ({
    variant,
    theme,
    accent,
    radius: `${radius}px`,
    side,
    align
  }), [accent, align, radius, side, theme, variant]);
  const liveHooks = useMemo(() => ({
    event: trackEvents ? event => console.log("Assistant event", event) : null,
    error: reportErrors ? error => console.error("Assistant error", error) : null
  }), [reportErrors, trackEvents]);
  const updateMountedWidget = useCallback(() => {
    if (!window.MintlifyAssistant) return;
    const liveTheme = appearance.theme === "system" ? document.documentElement.classList.contains("dark") ? "dark" : "light" : appearance.theme;
    void window.MintlifyAssistant.update({
      appearance: {
        ...appearance,
        theme: liveTheme
      },
      hooks: liveHooks
    }).catch(() => {});
  }, [appearance, liveHooks]);
  useEffect(() => {
    updateMountedWidget();
    window.addEventListener("mintlify-assistant-ready", updateMountedWidget);
    return () => {
      window.removeEventListener("mintlify-assistant-ready", updateMountedWidget);
    };
  }, [updateMountedWidget]);
  const configLines = ["{", `  id: '${EXAMPLE_WIDGET_ID}',`, "  appearance: {", `    variant: '${variant}',`, `    theme: '${theme}',`, `    accent: '${accent}',`, `    radius: '${radius}px',`, `    side: '${side}',`, `    align: '${align}',`, "  },"];
  if (trackEvents || reportErrors) {
    configLines.push("  hooks: {");
    if (trackEvents) {
      configLines.push("    event(event) {", "      console.log('Assistant event', event);", "    },");
    }
    if (reportErrors) {
      configLines.push("    error(error) {", "      console.error('Assistant error', error.code, error);", "    },");
    }
    configLines.push("  },");
  }
  configLines.push("}");
  const configCode = configLines.join("\n");
  const indentedConfig = configCode.split("\n").join("\n  ");
  const htmlCode = `<script
  type="module"
  src="${EMBED_URL}"
></script>
<script type="module">
  await window.MintlifyAssistant.init(${indentedConfig});
</script>`;
  const nextCode = `'use client';

import Script from 'next/script';

const ASSISTANT_CONFIG = ${configCode};

export const AssistantWidget = () => (
  <Script
    type="module"
    src="${EMBED_URL}"
    onReady={() => {
      void window.MintlifyAssistant.init(ASSISTANT_CONFIG);
    }}
  />
);`;
  const installCode = installTarget === "html" ? htmlCode : nextCode;
  return <div className="not-prose my-6 overflow-hidden rounded-xl border border-gray-950/10 bg-white dark:border-white/10 dark:bg-transparent">
      <div className="px-5 py-4">
        <div className="text-sm font-medium text-gray-950 dark:text-white">
          Widget playground
        </div>
        <p className="mt-0.5 text-sm text-gray-600 dark:text-gray-400">
          Changes apply to the widget on this page.
        </p>
      </div>

      <div className="border-t border-gray-950/10 px-5 py-5 dark:border-white/10">
        <div className="space-y-6">
          <fieldset>
            <legend className="mb-2 text-sm font-medium text-gray-950 dark:text-white">
              Presentation
            </legend>
            {renderSegmentedControl({
    ariaLabel: "Presentation",
    value: variant,
    options: PRESENTATION_OPTIONS,
    onChange: setVariant
  })}
          </fieldset>

          <div className="grid gap-4 sm:grid-cols-2">
            {renderSelectField({
    label: "Theme",
    value: theme,
    options: THEME_OPTIONS,
    onChange: setTheme
  })}

            <label className="flex min-w-0 flex-col text-sm font-medium text-gray-700 dark:text-gray-300">
              <span>Accent</span>
              <span className="flex h-10 items-center gap-2 rounded-xl border border-gray-950/10 px-2 dark:border-white/10">
                <input type="color" value={accent} onChange={event => setAccent(event.target.value)} aria-label="Accent color" className="h-7 w-8 cursor-pointer rounded border-0 bg-transparent p-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40" />
                <span className="font-mono text-xs font-normal text-gray-600 dark:text-gray-400">
                  {accent}
                </span>
              </span>
            </label>
          </div>

          <label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
            <span className="mb-2 flex items-center justify-between">
              Corner radius
              <output className="font-mono text-xs font-normal text-gray-500 dark:text-gray-400">
                {radius}px
              </output>
            </span>
            <input type="range" min="0" max="32" step="2" value={radius} onChange={event => setRadius(Number.parseInt(event.target.value))} className="block w-full accent-primary" />
          </label>

          <div className="grid gap-4 sm:grid-cols-2">
            {renderSelectField({
    label: "Trigger side",
    value: side,
    options: SIDE_OPTIONS,
    onChange: setSide
  })}
            {renderSelectField({
    label: "Trigger alignment",
    value: align,
    options: ALIGN_OPTIONS,
    onChange: setAlign
  })}
          </div>

          <fieldset>
            <legend className="text-sm font-medium text-gray-950 dark:text-white">
              Hooks
            </legend>
            <div className="mt-1 divide-y divide-gray-950/10 dark:divide-white/10">
              {renderToggleRow({
    label: "Lifecycle events",
    description: "Observe open, close, ask, update, and navigation events.",
    checked: trackEvents,
    onChange: setTrackEvents
  })}
              {renderToggleRow({
    label: "Structured errors",
    description: "Receive stable error codes and retry metadata.",
    checked: reportErrors,
    onChange: setReportErrors
  })}
            </div>
          </fieldset>
        </div>
      </div>

      <div className="border-t border-gray-950/10 bg-gray-50/60 p-5 dark:border-white/10 dark:bg-white/[0.02]">
        <div className="mb-3 flex flex-wrap items-center justify-between gap-3">
          <div>
            <div className="text-sm font-medium text-gray-950 dark:text-white">
              Install
            </div>
            <div className="mt-0.5 text-sm text-gray-600 dark:text-gray-400">
              Copy the generated setup for your stack.
            </div>
          </div>
          {renderSegmentedControl({
    ariaLabel: "Installation target",
    value: installTarget,
    options: INSTALL_OPTIONS,
    onChange: setInstallTarget
  })}
        </div>

        <CodeBlockComponent language={installTarget === "html" ? "html" : "jsx"} filename={installTarget === "html" ? "index.html" : "assistant-widget.jsx"} wrap>
          {installCode}
        </CodeBlockComponent>
      </div>
    </div>;
};

export const WidgetCodeBlock = ({children, ...props}) => <CodeBlock {...props}>{children}</CodeBlock>;

Use `@mintlify/assistant-widget` to add your Mintlify assistant to any website or web application. The hosted package owns its trigger and renders inside a closed Shadow DOM, preventing your application styles from changing the widget.

The only required browser option is the public Widget ID from your deployment's **Settings > Widget** page. The enabled state, allowed origins, starter questions, attachments, and bot protection remain managed in the Mintlify dashboard.

<Info>
  Replace `YOUR_WIDGET_ID` in the generated code with the public Widget ID from
  your deployment.
</Info>

## Install and configure

Use the playground to configure the presentation, visual options, and observer hooks. The installation block updates as you change each option.

<AssistantWidgetPlayground CodeBlockComponent={WidgetCodeBlock} />

Module scripts are deferred and run in document order. Keep the hosted loader before the initialization block when installing the widget with HTML.

## Use a custom trigger

Await `init()` before calling other methods. You can keep the built-in trigger or open the configured presentation from any button in your application.

```js theme={null}
await window.MintlifyAssistant.init({
  id: "YOUR_WIDGET_ID",
});

document.querySelector("#help-button").addEventListener("click", () => {
  void window.MintlifyAssistant.open({
    source: "help-button",
    focus: true,
  });
});
```

To open the widget and immediately send a question, call `ask()`:

```js theme={null}
await window.MintlifyAssistant.ask("How do I authenticate?", {
  source: "authentication-guide",
  open: true,
  focus: true,
});
```

The `source` value is included in event metadata and requests, which lets you distinguish built-in interactions from your custom entry points.

## Update a mounted widget

Use `update()` to change appearance, labels, or hooks without clearing the current conversation. Only the supplied fields change.

```js theme={null}
await window.MintlifyAssistant.update({
  appearance: {
    theme: "dark",
    accent: "#7c3aed",
  },
  labels: {
    title: "Docs copilot",
    trigger: "Ask docs",
  },
});
```

Pass `null` to restore a field or group to its default:

```js theme={null}
await window.MintlifyAssistant.update({
  appearance: {
    accent: null,
  },
  hooks: null,
});
```

Changing `identity` starts a new conversation. Changing the Widget ID or API endpoint requires calling `destroy()` before a new `init()`.

## Configuration reference

### Appearance

| Option                     | Values                                                         | Description                                                                                             |
| -------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `variant`                  | `widget`, `modal`, `panel`                                     | Controls whether the assistant opens as an anchored popover, centered dialog, or responsive side panel. |
| `theme`                    | `light`, `dark`, `system`                                      | Sets the widget color scheme. The default is `system`.                                                  |
| `accent`                   | CSS color                                                      | Sets the color of primary controls.                                                                     |
| `radius`                   | CSS border radius                                              | Sets the panel radius, such as `18px`.                                                                  |
| `font`                     | CSS font family                                                | Uses a font already loaded by your application. The default is bundled Inter.                           |
| `side`                     | `top`, `bottom`, `left`, `right`, `inline-start`, `inline-end` | Positions the built-in trigger on a screen edge.                                                        |
| `align`                    | `start`, `center`, `end`                                       | Aligns the trigger along its selected edge.                                                             |
| `dismissOnInteractOutside` | boolean                                                        | Controls whether pointer or focus interactions outside close the assistant.                             |
| `logo`                     | URL or `{ light, dark }`                                       | Replaces the default Mintlify mark.                                                                     |
| `zIndex`                   | number                                                         | Changes the stacking order of the widget host.                                                          |

Arbitrary CSS and neutral-palette overrides are not supported. The closed Shadow DOM protects both your application and the widget from cross-site style regressions.

### Hooks

```js theme={null}
hooks: {
  event(event) {
    console.log(event.type, event.actor, event.source);
  },
  error(error) {
    console.error(error.code, error.retryable, error.status);
  },
}
```

The `event` hook receives lifecycle and interaction metadata for `init`, `open`, `close`, `ask`, `update`, `reset`, `navigate`, and `destroy`. Events do not include question text, identity, session, or CAPTCHA tokens.

The `error` hook receives a stable `code`, a `retryable` boolean, and an optional HTTP `status`. Exceptions thrown by either hook do not interrupt the widget.

## Browser API

| Method                   | Description                                                                        |
| ------------------------ | ---------------------------------------------------------------------------------- |
| `init(config)`           | Loads and mounts the widget. This is the readiness promise for every other method. |
| `open(options)`          | Opens the configured presentation.                                                 |
| `close()`                | Closes the widget.                                                                 |
| `ask(question, options)` | Opens the widget if requested and sends a question.                                |
| `update(config)`         | Deep-patches mutable identity, appearance, label, and hook settings.               |
| `reset()`                | Starts a fresh conversation.                                                       |
| `destroy()`              | Removes the widget and releases its browser resources.                             |

Conversation snapshots remain private to the widget. Each method resolves to `void`.

## Content Security Policy

If your site uses a Content Security Policy, allow the origins required by your enabled widget features:

* `script-src`: `https://cdn.jsdelivr.net`, `https://challenges.cloudflare.com`, and `https://js.hcaptcha.com`
* `connect-src`: `https://api.mintlify.com`, `https://challenges.cloudflare.com`, and `https://*.hcaptcha.com`
* `frame-src`: `https://challenges.cloudflare.com` and `https://*.hcaptcha.com`
* `style-src`: `https://cdn.jsdelivr.net` and the nonce passed to `init()`
* `font-src`: `https://cdn.jsdelivr.net`
* `img-src`: any origins used by `appearance.logo`

A strict `script-src` policy must still authorize both the loader and initialization script. Passing `nonce` to `init()` propagates it only to resources the widget creates after initialization.
