Reference

The ask dialog

AskDialog props, the launcher button, shortcuts, theming, accessibility and the useAsk hook.

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"k"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"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.
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.
challengenone{ turnstile: '<site key>' }: solve a bot check before each question.

Opening the dialog

In React, the dialog opens with ⌘K on macOS and Ctrl+K elsewhere. The plugins and the script tag use ⌘I or Ctrl+I instead, 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="j", 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: 'j' }, and with the script tag it is data-shortcut="j".

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 about 3 KB with Docusaurus, and from about 88 KB to under 2 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.

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.

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.

TSX
<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.
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, which follows the system's light or dark setting. 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.

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.