Skip to main content

The ask dialog

AskDialog from ondocs/react is a command palette built on Radix Dialog and cmdk. The Docusaurus, Astro and Starlight plugins and the script tag render the same component, so everything here applies to them too, through their dialog options or data-* attributes.

AskDialog props​

PropDefaultNotes
endpoint/api/askWhere questions are posted. Also headers and a custom fetch.
open, defaultOpen, onOpenChangeuncontrolledControlled or uncontrolled.
shortcut"i"Opens the dialog with ⌘ or Ctrl; false disables it.
triggernoneAn element that opens the dialog, such as a search button.
launcherfalsetrue or a label: a floating "Ask AI" button with the shortcut on it.
suggestions[]Questions offered before typing, filtered as you type.
onNavigatebrowser navigation(url, event) for citations and sources; call preventDefault() to route yourself.
theme"auto"Follows data-theme on <html>, then the system; "light" or "dark" to pin it.
classNamesnoneExtra classes per part: overlay, content, input, list, item, answer, sources, footer.
title"Ask this site"The dialog's accessible name.
placeholder"Ask a question…"The input's placeholder.
footera disclaimer and keyboard hintsReplaces the footer.
links"all""sources" keeps only the answer's links to its source pages; citations always link.
sourceUrlsnone{ pages, base }: where citations and links go when the endpoint answers from another site's index.
labelsEnglishEvery string the dialog and its button show or announce; see Labels and languages.
localenoneThe page's locale, sent with each question, for an endpoint with an index per locale.
followUpstrueSend the thread with each question, so the endpoint can answer follow-ups; false asks each alone.
questionnoneAsked as the dialog opens with it, and when it changes while open, each time in a new thread.
challengenone{ turnstile: '<site key>' }: solve a bot check before each question.

Opening the dialog​

The dialog opens with ⌘I on macOS and Ctrl+I elsewhere, in React, in the plugins and with the script tag, so ⌘K stays with the site's search. The shortcut does nothing while a text field or editor has focus.

To change the keyboard shortcut, pass another key, as in shortcut="k" on a site whose only search is this dialog, or turn it off with shortcut={false} and open the dialog from a trigger element or the launcher button instead. In the plugins, the same option is dialog: { shortcut: 'k' }, and with the script tag it is data-shortcut="k".

Open it from your own button​

Any element with a data-ondocs-open attribute opens the dialog the page has: the Docusaurus, Starlight or VitePress plugin's, the script tag's, or one from mountAskDialog. With a value, the attribute is a question, which the dialog asks in a new thread. A form with the attribute asks what its question field holds, or its first input, when it is submitted. There is no script to write, so it works in Markdown, MDX, a theme's template or an HTML navbar item:

<button type="button" data-ondocs-open>Ask AI</button>
<button type="button" data-ondocs-open="Do I need a vector database?">
Do I need a vector database?
</button>
<form data-ondocs-open>
<input name="question" placeholder="Ask these docs a question…" />
</form>

From code, openAskDialog(question?) from ondocs/embed does the same in any framework, and so does window.Ondocs.open(question?) on a page with the script tag. A pointer over the element, or focus on it, loads the dialog's code, as on the launcher. The plugins' navbar option puts such a button in the navbar for you: see Docusaurus, Starlight and VitePress. For its look on a button of your own, import ondocs/embed/button.css and give the button the ondocs-button class, with its label in a <span class="ondocs-button-label">.

In React, with a controlled open, the question prop does the same: set both to open the dialog on a question.

When the dialog's code loads​

A page carries only the dialog's shortcut and, with launcher or trigger, its button. The dialog itself, Radix Dialog, cmdk and the answer, is a separate chunk that your bundler splits off, and it loads the first time the dialog is wanted: a pointer over or focus on its button or trigger, the shortcut, or open. A shortcut pressed while it loads is kept, so the dialog opens, with focus in its input, once it arrives; Escape before then cancels it. Focus goes back to where it was when the dialog closes, or to the button that opened it. Call loadAskDialog() from ondocs/react to load it sooner, such as when the page is idle.

In the plugins' sites this takes the JavaScript ondocs adds to each page from about 22 KB to 3.9 KB with Docusaurus, and from about 88 KB to 2.4 KB with Starlight, which has no React of its own; see Benchmarks.

The launcher button​

launcher adds a floating "Ask AI" button in the corner of the page, with the shortcut on it. Pass a string to change its label, as in launcher="Ask the docs", and import ondocs/embed/launcher.css for its look, or style .ondocs-launcher yourself. The plugins and the script tag show it by default; there, launcher: false (or data-launcher="false") hides it, and so does the plugins' navbar: true, which puts the button in the navbar instead.

Citations and navigation​

Citations in the answer and the entries in the sources list link to the exact section they came from. Pass onNavigate to route with your framework's client router, for example router.push(url) in Next.js. The dialog closes after a click either way.

When the endpoint answers from another site's index, as on a copy of that site's docs that asks its endpoint, the sources carry that site's URLs. sourceUrls sends them to the same pages on this site: pages maps a page's URL in the index to its URL here, and the anchor is kept; any other relative URL resolves against base, the other site's address, so it opens there. It applies to citations, the sources list and links in the answer, and in the plugins it is dialog: { sourceUrls }:

dialog: {
sourceUrls: {
pages: { '/docs/introduction': '/', '/docs/getting-started': '/getting-started' },
base: 'https://docs.acme.dev',
},
},

Follow-up questions​

The dialog keeps the thread. Once an answer is on screen, typing another question offers "Follow up", and the answer stays in view while you type. The new question goes to the endpoint with the last three questions and their answers, so it can lean on them, as in "and on Netlify?": the endpoint rewrites it into one that stands on its own before it searches (see Follow-up questions). Earlier questions and answers stay above the current one, each with its own citations, which open its own sources. "New question" starts a new thread, with an empty input. followUps={false} asks every question on its own.

Rating answers​

When the endpoint takes feedback (its onFeedback), the dialog asks "Was this helpful?" under each answer, with thumbs up and down. After a rating it thanks the reader and offers "Add a comment", a one-line field that Enter sends. Both go to the endpoint, with the answer's id, and nowhere else. useAsk has the same as rate(rating, comment?), with feedbackEnabled and rating in its state. See Measure and improve answers.

Labels and languages​

Every string the dialog and its button show or announce, visible or for screen readers, is a label: pass any of them as labels, and the rest stay in English. A {name} in a label is filled in: {count} and {n} with numbers, {title} with a page's title, {status} with an HTTP status. title, placeholder and launcher win over their labels.

<AskDialog
launcher
locale="fr"
labels={{
launcher: 'Demander',
placeholder: 'Posez une question…',
newQuestion: 'Nouvelle question',
}}
/>

The endpoint's own words, its "I don't know" answer, its rate limit and budget messages, its other errors and the message for an answer the model failed to write, show as it sends them, so set them there (noAnswerMessage, budget.message), or replace them in the dialog with noAnswer, rateLimited, budgetExceeded, serverError and answerFailed. The plugins take labels per locale; see Docusaurus and Astro and Starlight.

LabelDefault
launcherAsk AI
controlKeyCtrl
titleAsk this site
descriptionType a question and press Enter. Answers are generated from this site’s pages and cite them.
placeholderAsk a question…
askAsk
followUpFollow up
suggestedSuggested
listSuggestions
stopStop
escapeKeyesc
footerAnswers come from this site and can be wrong. Check the sources.
footerNoSourcesAnswers come from this site and can be wrong.
footerAskask
footerCloseclose
searchingSearching the site…
writingWriting an answer…
noAnswerFoundNo answer found on this site.
answerReadyOneAnswer ready, citing 1 source.
answerReadyManyAnswer ready, citing {count} sources.
answerTruncatedAnswer ready, but cut short at its length limit.
truncatedNoticeThis answer reached its length limit and may be incomplete.
retryRetry
sourcesSources
citationSource {n}: {title}
newQuestionNew question
helpfulQuestionWas this helpful?
helpfulYes, it helped
notHelpfulNo, it did not help
thanksThanks for the feedback.
addCommentAdd a comment
commentComment
commentPlaceholderWhat was missing or wrong?
sendSend
thanksCommentThanks for the comment.
feedbackFailedThe feedback could not be sent.
errorNetworkCould not reach the server. Check your connection.
errorUnavailableAnswers aren’t available here right now.
errorNoBodyThe response had no body.
errorInterruptedThe answer was interrupted. Please try again.
errorCutOffThe answer was cut off. Please try again.
challengeRequiredThe check that you’re not a bot didn’t load. Reload the page and try again.
challengeFailedThe check that you’re not a bot failed. Please try again.
errorUnknownSomething went wrong.
noAnswerthe endpoint's own words
rateLimitedthe endpoint's own words
budgetExceededthe endpoint's own words
serverErrorthe endpoint's own words
answerFailedthe endpoint's own words

Theming the dialog​

Import ondocs/react/styles.css for the default theme. With theme="auto", the default, it follows a data-theme="light" or "dark" on <html>, as Docusaurus, Starlight and VitePress set it, and the system's light or dark setting on a page without one. To change the dialog's colors, set its CSS custom properties: every color, radius and font is one, so .ask-dialog { --ask-accent: #7c3aed; --ask-radius: 8px; } is a complete rebrand. To style it from scratch, skip the stylesheet: every part has a stable ask-* class, and classNames adds your own, which suits Tailwind.

Accessibility​

Focus moves into the dialog when it opens and returns to where it was when it closes. The answer region is aria-live="polite" and aria-busy while text streams, and a separate status region announces when the answer is ready. All animation is turned off under prefers-reduced-motion. The test suite runs axe on the dialog before and after it answers, and checks that every text color in the light and dark themes keeps a contrast of at least 4.5:1.

While an answer streams​

Sources arrive before the first word, and the answer renders as it streams, batched to one update per animation frame. Closing the dialog, by Escape, a click outside or its parent, stops the answer in flight, so the model is not left generating for nobody. When an answer stops at the model's output limit, the dialog says it may be incomplete. Once an answer is complete, the sources it does not cite are dimmed, and an answer that cites none shows no sources.

The useAsk hook​

useAsk({ endpoint }) is the hook behind the dialog, for building your own interface. It returns ask, stop and reset, plus status (idle, loading, streaming, done or error), question, answer, sources, refused, truncated, retrieval, error and turns, the thread's earlier questions with their answers and sources. ask adds the answer on screen to turns and sends the thread with the next question, and reset starts a new thread; followUps: false keeps no thread. error.kind is rate-limited, http, network or stream, with retryAfter for rate limits. stop() keeps the partial answer.

AskAnswer renders an answer with clickable citations from answer and sources. It renders a small Markdown subset, never HTML, and only turns [n] into a link when source n exists.