# ask-ai-widget
**"Ask AI about this page" buttons in one tag.** A row of ChatGPT, Claude, Perplexity, Gemini, Grok, Mistral and DeepSeek buttons (also called "Open in ChatGPT", "Summarize with AI" or AI share buttons). Each opens the assistant in a new tab with a prompt about the current page. No backend, no API keys, no tracking, zero dependencies.
- **Any site:** one `
```
That is the whole integration. Every button opens its service with a prompt that contains the page title and URL. For production, pin an exact version (`ask-ai-widget@0.1.0`): while the major version is 0, `@0` can receive breaking changes.
Make it answer a visitor's own question instead:
```html
```
## Let your AI coding agent do it
Copy this prompt (use the copy button in the corner of the block) and paste it into Claude Code, Cursor, Copilot, Codex or any other coding agent working in your project:
```text
Add the ask-ai-widget "Ask AI" button row to this project.
1. Install it with this project's package manager: npm install ask-ai-widget
(For a plain HTML site with no build step, use the CDN script instead:
https://cdn.jsdelivr.net/npm/ask-ai-widget@0/dist/ask-ai.min.js)
2. Read node_modules/ask-ai-widget/llms.txt before writing any code. It lists
the right import for each framework, every option, and the common mistakes.
3. Detect the framework and use its entry: React/Next.js -> ask-ai-widget/react,
Vue/Nuxt -> ask-ai-widget/vue, Svelte/SvelteKit -> ask-ai-widget/svelte,
anything else -> the element from ask-ai-widget/element.
4. Place the widget where I ask below (or suggest a spot, e.g. under the docs
page title). Use label "Ask AI about " and keep the default
page context (title + URL) unless I say otherwise.
5. Prompts must be strings or synchronous functions. Never await inside them,
or browsers block the new tab.
6. With SSR (Next.js, Nuxt, SvelteKit), import "ask-ai-widget/style.css" in the
root layout, and pass page={{ url, title }} so server and client links match.
7. Style it only through --aai-* CSS variables or the .aai__* classes; don't
copy or edit the library CSS.
8. Show me the diff and how to see it running.
Where and how I want it:
```
The same guide is published at [`llms.txt`](llms.txt), so agents that read llms.txt files pick it up on their own.
## Contents
- [Let your AI coding agent do it](#let-your-ai-coding-agent-do-it)
- [Install](#install)
- [Frameworks](#frameworks): [HTML](#html--web-component), [Vanilla JS](#vanilla-js), [React / Next.js](#react--nextjs), [Vue / Nuxt](#vue--nuxt), [Svelte](#svelte), [Astro and others](#astro-and-other-frameworks)
- [Two modes: predefined prompt or user question](#two-modes)
- [Prompt composition](#prompt-composition)
- [Services](#services)
- [Dropdown menu](#dropdown-menu)
- [Label position](#label-position)
- [Translations](#translations)
- [Styling](#styling)
- [Bundle size and tree-shaking](#bundle-size)
- [Events and analytics](#events-and-analytics)
- [API reference](#api-reference)
- [Accessibility](#accessibility), [Privacy](#privacy), [Browser support](#browser-support)
- [FAQ and troubleshooting](#faq-and-troubleshooting)
- [Trademarks](#trademarks), [Contributing](#contributing), [License](#license)
## Install
```bash
npm install ask-ai-widget
# pnpm add ask-ai-widget
# yarn add ask-ai-widget
```
React, Vue and Svelte are optional peer dependencies. You only need the one you use. The package ships ESM, CJS and `.d.ts` files for every entry.
| Import | What you get |
| --- | --- |
| `ask-ai-widget` | Core API (`mount`, `getItems`, `buildPrompt`, ...) with all 7 services built in |
| `ask-ai-widget/element` | Registers `` (side effect) and exports the element class |
| `ask-ai-widget/react` | `AskAI` component |
| `ask-ai-widget/vue` | `AskAI` component and `plugin` |
| `ask-ai-widget/svelte` | `askAI` action |
| `ask-ai-widget/style.css` | The complete stylesheet as a file, row and menu layout (optional, for zero-FOUC SSR) |
| `ask-ai-widget/style-row.css` | The same without the dropdown rules, for SSR pages that only use the row layout |
| `ask-ai-widget/lite`, `lite/element`, `lite/react`, `lite/vue`, `lite/svelte` | Same APIs without the built-in logos, for [tree-shaking](#bundle-size) |
| `ask-ai-widget/services` | The service objects: `chatgpt`, `claude`, `perplexity`, `gemini`, `grok`, `mistral`, `deepseek`, `builtins` |
| CDN `dist/ask-ai.min.js` | One file, global `AskAI`, auto-defines ``, auto-mounts `[data-ask-ai]` |
## Frameworks
### HTML / Web component
```html
```
With a bundler, `import "ask-ai-widget/element";` registers the tag instead.
`` renders into its own light DOM, so your CSS applies. Configure it with attributes, with JS properties, or both (properties win):
| Attribute | Property | Value |
| --- | --- | --- |
| `services` | `services` | comma list of keys: `"claude,chatgpt"` |
| `label` | `label` | text next to the buttons |
| `label-position` | `labelPosition` | `left` (default), `top`, `right`, `bottom`, `none`, see [Label position](#label-position) |
| `variant` | `variant` | `soft` (default), `outline`, `solid`, `ghost`, `brand`, or your own name |
| `shape` | `shape` | `circle` (default), `rounded`, `square` |
| `size` | `size` | `sm`, `md` (default), `lg` |
| `theme` | `theme` | `light` (default), `dark`, `auto` |
| `layout` | `layout` | `row` (default) or `menu`, see [Dropdown menu](#dropdown-menu) |
| `menu-align` | `menuAlign` | menu layout: `start` (default) or `end` |
| `menu-icons` | `menuIcons` | menu layout: `stack` (default, logos in the trigger) or `none` |
| `show-labels` | `showLabels` | boolean attribute |
| `prompt` | `prompt` | template string, see [Prompt composition](#prompt-composition) |
| `instructions` | `instructions` | template string placed before the prompt |
| `context` | `context` | template string, or `none` / `false` to drop it |
| `references` | `references` | comma list of URLs |
| `template` | `template` | how the parts are joined |
| `vars` | `vars` | JSON object: `vars='{"product":"Example"}'` |
| `page` | `page` | JSON object: `page='{"url":"https://example.com/x","title":"X"}'` |
| `max-length` | `maxLength` | number, default 4000 |
| `input` | `input` | bare `input` = built-in field; `input="#q"` = existing field; `input="false"` = off |
| `placeholder`, `required`, `multiline` | part of `input` | options of the built-in field (`placeholder` alone also turns the field on). With `input="#q"`, only `required` applies: `input="#q" required="false"` allows an empty question |
| `strings` | `strings` | JSON object that translates the widget's own texts: `strings='{"ask":"Frage {service}"}'`, see [Translations](#translations) |
| `submit-service` | `submitService` | key opened by Enter in the input |
| `class-name` | (use `class`) | extra classes added to the root. `class` itself works too |
| `unstyled` | `unstyled` | boolean attribute, skip the default CSS |
| - | `onOpen` | function, property only |
| - | `options` | `el.options = { ... }` sets several properties at once |
Properties may be functions and are batched into one render:
```js
const el = document.querySelector("ask-ai");
el.prompt = (ctx) => `Explain ${ctx.title} to a beginner`;
el.onOpen = ({ service }) => console.log(service);
```
No JS needed for a CDN page with several widgets either: `` is auto-mounted with the same attribute grammar using a `data-` prefix (`data-show-labels`, `data-layout="menu"`, `data-input`, ...). Call `AskAI.init()` after adding more to the DOM.
### Vanilla JS
```js
import { mount } from "ask-ai-widget";
const widget = mount("#ask-ai", {
services: ["claude", "chatgpt"],
label: "Ask AI about this page",
prompt: (ctx) => `Summarize ${ctx.title} in 3 bullets.`,
onOpen: ({ service, prompt }) => console.log(service, prompt),
});
widget.update({ variant: "outline" }); // merges options and re-renders
widget.destroy(); // removes DOM, listeners, classes
```
`mount(target, options)` accepts an element or a selector and returns `{ element, update, destroy }`, or `null` when the target does not exist. Mounting the same element twice replaces the first instance.
### React / Next.js
```tsx
import { AskAI } from "ask-ai-widget/react";
export function Share() {
return (
track(service)}
/>
);
}
```
The component renders real `` elements on the server, forwards `ref` to the root `