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

# Mintlify widget

> Install and configure the Mintlify widget to embed an AI assistant trained on your docs content into any website, web app, or dashboard.

export const AssistantWidgetPlayground = ({children, 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 PREVIEW_READY_MESSAGE = "mintlify-assistant-playground:ready";
  const PREVIEW_UPDATE_MESSAGE = "mintlify-assistant-playground:update";
  const PREVIEW_STATE_MESSAGE = "mintlify-assistant-playground:state";
  const SUPPORT_EMAIL = "hi@mintlify.com";
  const STARTER_QUESTIONS = ["How do I get started with Mintlify?", "How do I customize my docs?", "How do I deploy my docs?"];
  const VARIANT_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(false);
  const [reportErrors, setReportErrors] = useState(false);
  const [previewHostReady, setPreviewHostReady] = useState(false);
  const [previewUrl, setPreviewUrl] = useState(null);
  const [previewStatus, setPreviewStatus] = useState("loading");
  const previewRef = useRef(null);
  const previewHostRef = useRef(null);
  useEffect(() => {
    const pageMatch = window.location.pathname.replace(/\/$/, "").match(/^(.*?)(\/[a-z]{2}(?:-[A-Za-z]{2,4})?)?\/assistant\/widget$/);
    const basePath = pageMatch?.[1] ?? "";
    const locale = pageMatch?.[2] ?? "";
    const mode = document.documentElement.classList.contains("dark") ? "dark" : "light";
    setPreviewUrl(`${basePath}/_minimal${locale}/assistant/widget-preview?mode=${mode}`);
  }, []);
  useEffect(() => {
    const removeRootWidget = () => {
      const rootWidget = document.querySelector("body > mintlify-assistant");
      if (!rootWidget) return false;
      const destroyPromise = window.MintlifyAssistant?.destroy();
      void destroyPromise?.catch(() => {});
      rootWidget.remove();
      return true;
    };
    const rootWidgetObserver = new MutationObserver(removeRootWidget);
    removeRootWidget();
    rootWidgetObserver.observe(document.body, {
      childList: true
    });
    return () => rootWidgetObserver.disconnect();
  }, []);
  useEffect(() => {
    const host = previewHostRef.current;
    if (!host) return undefined;
    const markReady = height => {
      if (height > 0) setPreviewHostReady(true);
    };
    markReady(host.getBoundingClientRect().height);
    if (typeof ResizeObserver === "undefined") {
      setPreviewHostReady(true);
      return undefined;
    }
    const observer = new ResizeObserver(entries => {
      markReady(entries[0]?.contentRect.height ?? 0);
    });
    observer.observe(host);
    return () => observer.disconnect();
  }, []);
  const classNames = (...classes) => classes.filter(Boolean).join(" ");
  const renderSegmentedControl = ({ariaLabel, onChange, options, threeColumns = false, value}) => <div role="group" aria-label={ariaLabel} className="grid gap-1 rounded-lg bg-gray-100 p-1 dark:bg-white/10" style={{
    gridTemplateColumns: `repeat(${threeColumns ? 3 : options.length}, minmax(0, 1fr))`
  }}>
      {options.map(option => <button key={option.value} type="button" aria-pressed={value === option.value} onClick={() => onChange(option.value)} className={classNames("min-h-8 rounded-md px-2 py-1.5 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 className="mb-2">{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 updatePreview = useCallback(() => {
    const previewWindow = previewRef.current?.contentWindow;
    if (!previewWindow) return;
    const liveTheme = appearance.theme === "system" ? document.documentElement.classList.contains("dark") ? "dark" : "light" : appearance.theme;
    previewWindow.postMessage({
      type: PREVIEW_UPDATE_MESSAGE,
      trackEvents,
      reportErrors,
      appearance: {
        ...appearance,
        theme: liveTheme
      }
    }, window.location.origin);
  }, [appearance, reportErrors, trackEvents]);
  useEffect(() => {
    const handlePreviewMessage = event => {
      if (event.source !== previewRef.current?.contentWindow) return;
      if (event.data?.type === PREVIEW_READY_MESSAGE) {
        updatePreview();
        return;
      }
      if (event.data?.type === PREVIEW_STATE_MESSAGE) {
        setPreviewStatus(event.data.state === "error" ? "error" : "ready");
      }
    };
    const themeObserver = new MutationObserver(updatePreview);
    window.addEventListener("message", handlePreviewMessage);
    themeObserver.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ["class"]
    });
    updatePreview();
    return () => {
      window.removeEventListener("message", handlePreviewMessage);
      themeObserver.disconnect();
    };
  }, [updatePreview]);
  useEffect(() => {
    if (!previewHostReady || !previewUrl || previewStatus !== "loading") {
      return undefined;
    }
    const timeout = window.setTimeout(() => {
      setPreviewStatus(status => status === "loading" ? "error" : status);
    }, 20000);
    return () => window.clearTimeout(timeout);
  }, [previewHostReady, previewStatus, previewUrl]);
  const configLines = ["{", `  id: '${EXAMPLE_WIDGET_ID}',`, `  supportEmail: '${SUPPORT_EMAIL}',`, "  starterQuestions: [", ...STARTER_QUESTIONS.map(question => `    '${question}',`), "  ],", "  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="my-6 grid gap-8" data-assistant-playground-layout="">
      <div className="min-w-0">
        <div className="not-prose overflow-hidden rounded-xl border border-gray-950/10 dark:border-white/10">
          <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 preview.
            </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">
                  Variants
                </legend>
                {renderSegmentedControl({
    ariaLabel: "Variants",
    value: variant,
    options: VARIANT_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 className="mb-2">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">
                <fieldset>
                  <legend className="mb-2 text-sm font-medium text-gray-950 dark:text-white">
                    Trigger side
                  </legend>
                  {renderSegmentedControl({
    ariaLabel: "Trigger side",
    threeColumns: true,
    value: side,
    options: SIDE_OPTIONS,
    onChange: setSide
  })}
                </fieldset>

                <fieldset>
                  <legend className="mb-2 text-sm font-medium text-gray-950 dark:text-white">
                    Trigger alignment
                  </legend>
                  {renderSegmentedControl({
    ariaLabel: "Trigger alignment",
    value: align,
    options: ALIGN_OPTIONS,
    onChange: setAlign
  })}
                </fieldset>
              </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 p-5 dark:border-white/10">
            <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="jsx" filename={installTarget === "html" ? "index.html" : "assistant-widget.jsx"} wrap>
              {installCode}
            </CodeBlockComponent>
          </div>
        </div>
        {children ? <div className="mt-8">{children}</div> : null}
      </div>

      <aside className="not-prose" data-assistant-preview="">
        <div ref={previewHostRef} className="relative flex h-[42rem] min-h-0 flex-col overflow-hidden rounded-xl border border-gray-950/10 bg-transparent dark:border-white/10 lg:sticky lg:top-20 lg:h-[calc(100vh-6rem)]" data-assistant-preview-card="">
          {previewHostReady && previewUrl ? <iframe ref={previewRef} title="Live Assistant Widget preview" src={previewUrl} onLoad={updatePreview} scrolling="no" className="min-h-0 w-full flex-1 border-0 bg-transparent [color-scheme:light_dark] dark:[color-scheme:dark]" /> : null}
          {previewStatus !== "ready" ? <div aria-live="polite" role="status" className="pointer-events-none absolute inset-0 z-10 flex items-center justify-center gap-3 px-6 text-center text-sm text-gray-600 dark:text-gray-400">
              {previewStatus === "loading" ? <span aria-hidden="true" className="size-[18px] shrink-0 animate-spin rounded-full border-[1.5px] border-gray-300 border-t-gray-600 motion-reduce:animate-none dark:border-gray-600 dark:border-t-gray-300" /> : null}
              <span>
                {previewStatus === "loading" ? "Loading assistant preview..." : "The live preview could not load. It requires a deployed docs site and the browser console may have details."}
              </span>
            </div> : null}
        </div>
      </aside>
    </div>;
};

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

The [assistant](/assistant) answers questions on your Mintlify site. To embed the same capability on another site or web app, use the widget. With the widget, you can give your users access to AI chat trained on your content in your product dashboard, marketing site, support portal, or elsewhere.

Add the widget to any website or web application with a hosted script. The widget owns its trigger and renders inside a closed Shadow DOM, which prevents your application styles from affecting the widget.

The only required browser option is the public widget ID. Manage the enabled state, allowed origins, attachments, and bot protection in your dashboard. Set embed-specific starter questions and a support email in the browser configuration.

## Prerequisites

* A [Pro or Enterprise plan](https://mintlify.com/pricing?ref=assistant). The widget uses the same credits as the assistant.

## Enable the widget

1. Navigate to your deployment's [Widget](https://app.mintlify.com/settings/deployment/widget) page.
2. Enable the widget.
3. Add allowed origins where you embed the widget.
4. Copy the widget ID.

## Install and configure

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

<Info>
  Replace `YOUR_WIDGET_ID` in the generated code with the widget ID from the [Widget](https://app.mintlify.com/settings/deployment/widget) page of your dashboard.
</Info>

After you add the generated code to your site, reload the page. Confirm the trigger appears, then click it and send a test question to verify the widget is connected.

<AssistantWidgetPlayground CodeBlockComponent={WidgetCodeBlock}>
  <Warning>
    Module scripts defer and run in document order. Keep the hosted loader before the initialization block when you install the widget with HTML, or the widget fails to mount.
  </Warning>

  ## Open on initialization

  Set `defaultOpen` to `true` to open the widget immediately after its first mount:

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

  `defaultOpen` defaults to `false` and only applies to the first initialization. Calling `init()` again with the same widget ID and API endpoint does not reopen a widget that a visitor closed. Use `open()` and `close()` to control it after initialization.

  ## Use a custom trigger

  Await `init()` before calling other methods. 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",
    supportEmail: "hi@mintlify.com",
    starterQuestions: [
      "How do I get started with Mintlify?",
      "How do I customize my docs?",
      "How do I deploy my docs?",
    ],
  });

  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,
  });
  ```

  Event metadata and requests include the `source` value, which lets you distinguish built-in interactions from your custom entry points.

  ## Update a mounted widget

  Use `update()` to change appearance, labels, support email, starter questions, 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",
    },
    supportEmail: "support@example.com",
    starterQuestions: [
      "How do I get started?",
      "How do I manage my account?",
    ],
  });
  ```

  Pass `null` to restore a field or group to its default, remove the support email, or restore an empty starter-question list:

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

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

  You can supply `supportEmail` and `starterQuestions` during initialization and change them later with `update()`. These values apply to the current embed and do not inherit from your Mintlify dashboard.

  ## Configuration reference

  ### `AssistantConfig`

  Pass this object to `init()`.

  | Option             | Type                                          | Description                                                           |
  | ------------------ | --------------------------------------------- | --------------------------------------------------------------------- |
  | `id`               | string                                        | Public widget ID from the Mintlify dashboard.                         |
  | `endpoint`         | string                                        | Overrides the hosted widget API endpoint.                             |
  | `identity`         | string                                        | Signed end-user identity token. Omit for anonymous visitors.          |
  | `nonce`            | string                                        | CSP nonce copied to resources created by the widget.                  |
  | `defaultOpen`      | boolean                                       | Opens the widget on its first initialization. The default is `false`. |
  | `appearance`       | [`AssistantAppearance`](#assistantappearance) | Visual and presentation overrides.                                    |
  | `labels`           | [`AssistantLabels`](#assistantlabels)         | Customer-facing text overrides.                                       |
  | `supportEmail`     | string                                        | Sets the support address shown in the widget toolbar for this embed.  |
  | `starterQuestions` | string\[]                                     | Sets up to three empty-state prompts for this embed.                  |
  | `hooks`            | [`AssistantHooks`](#assistanthooks)           | Event and error observers.                                            |

  ### `AssistantAppearance`

  | 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.

  ### `AssistantLabels`

  | Option        | Values                     | Description                                                             |
  | ------------- | -------------------------- | ----------------------------------------------------------------------- |
  | `title`       | string or `null`           | Sets the panel header. The default is `Assistant`.                      |
  | `trigger`     | string or `null`           | Sets the compact widget and panel trigger text.                         |
  | `placeholder` | string or `null`           | Sets the composer and modal trigger placeholder.                        |
  | `disclaimer`  | string, `false`, or `null` | Sets the empty-state disclaimer. Pass `false` to hide it.               |
  | `suggestions` | string or `null`           | Sets the heading above starter questions. The default is `Suggestions`. |

  ### `AssistantHooks`

  ```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.

  ### `AssistantOpenOptions`

  Pass this optional object to `open()`.

  | Option   | Type    | Description                                                   |
  | -------- | ------- | ------------------------------------------------------------- |
  | `source` | string  | Customer-defined attribution included in events and requests. |
  | `focus`  | boolean | Focuses the composer after opening. The default is `true`.    |

  ### `AssistantAskOptions`

  Pass this optional object after the question string in `ask()`.

  | Option   | Type    | Description                                                   |
  | -------- | ------- | ------------------------------------------------------------- |
  | `source` | string  | Customer-defined attribution included in events and requests. |
  | `open`   | boolean | Opens the panel before sending. The default is `true`.        |
  | `focus`  | boolean | Focuses the composer when opening. The default is `true`.     |

  ### `AssistantUpdate`

  Pass this object to `update()`. Every field is optional, and `null` restores its default.

  | Option             | Type                                                    | Description                                                        |
  | ------------------ | ------------------------------------------------------- | ------------------------------------------------------------------ |
  | `identity`         | string or `null`                                        | Changes the signed identity and starts a new conversation.         |
  | `appearance`       | [`AssistantAppearance`](#assistantappearance) or `null` | Deep-patches appearance settings.                                  |
  | `labels`           | [`AssistantLabels`](#assistantlabels) or `null`         | Deep-patches customer-facing text.                                 |
  | `supportEmail`     | string or `null`                                        | Changes the support address. Pass `null` to remove it.             |
  | `starterQuestions` | string\[] or `null`                                     | Changes up to three prompts. Pass `null` to restore an empty list. |
  | `hooks`            | [`AssistantHooks`](#assistanthooks) or `null`           | Deep-patches event and error observers.                            |

  ## Browser API

  | Method                   | Parameter types                                       | Description                                                                        |
  | ------------------------ | ----------------------------------------------------- | ---------------------------------------------------------------------------------- |
  | `init(config)`           | [`AssistantConfig`](#assistantconfig)                 | Loads and mounts the widget. This is the readiness promise for every other method. |
  | `open(options)`          | [`AssistantOpenOptions`](#assistantopenoptions)       | Opens the configured presentation.                                                 |
  | `close()`                | —                                                     | Closes the widget.                                                                 |
  | `ask(question, options)` | string, [`AssistantAskOptions`](#assistantaskoptions) | Opens the widget if requested and sends a question.                                |
  | `update(config)`         | [`AssistantUpdate`](#assistantupdate)                 | Deep-patches mutable identity, appearance, copy, and observer 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:

  | Directive                                    | Source                              | Required for                |
  | -------------------------------------------- | ----------------------------------- | --------------------------- |
  | `script-src`                                 | `https://cdn.jsdelivr.net`          | Widget loader and runtime   |
  | `connect-src`                                | `https://api.mintlify.com`          | Widget API                  |
  | `style-src`                                  | `https://cdn.jsdelivr.net`          | Widget style sheet          |
  | `font-src`                                   | `https://cdn.jsdelivr.net`          | Optional bundled Inter font |
  | `script-src`, `connect-src`, and `frame-src` | `https://challenges.cloudflare.com` | Turnstile bot protection    |
  | `script-src`                                 | `https://js.hcaptcha.com`           | hCaptcha bot protection     |
  | `connect-src` and `frame-src`                | `https://*.hcaptcha.com`            | hCaptcha bot protection     |

  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.
</AssistantWidgetPlayground>
