Composable API
The composable API gives you control over where DocSearch renders, when its modal code loads, and how the rest of your application opens or closes it.
Use this API in React applications. For the connected component reference, see the modal package overview.
Choose a modal
DocSearch v5 provides two modal components. Render one modal for each DocSearch provider.
| Component | Use it for |
|---|---|
DocSearchModal | Keyword search without Ask AI |
DocSearchAskAiModal | Keyword search and Ask AI in the same modal |
DocSearchAskAiModal includes keyword search. Don't render both modal components to add Ask AI.
The composable components come from three packages:
@docsearch/coreprovidesDocSearch, shared state, keyboard handling, and the imperative ref.@docsearch/modalprovides the provider-connected button and modal components.@docsearch/cssprovides the styles.
Install the packages
Install matching v5 versions of the DocSearch packages:
- npm
- Yarn
- pnpm
- Bun
npm install @docsearch/core@^5.0.0-beta @docsearch/modal@^5.0.0-beta @docsearch/css@^5.0.0-beta
yarn add @docsearch/core@^5.0.0-beta @docsearch/modal@^5.0.0-beta @docsearch/css@^5.0.0-beta
pnpm add @docsearch/core@^5.0.0-beta @docsearch/modal@^5.0.0-beta @docsearch/css@^5.0.0-beta
bun add @docsearch/core@^5.0.0-beta @docsearch/modal@^5.0.0-beta @docsearch/css@^5.0.0-beta
Add keyword search
Wrap the button and keyword modal in one DocSearch provider. Pass a public search-only API key to the provider; its descendants use these credentials by default.
import { DocSearch } from '@docsearch/core';
import { DocSearchButton, DocSearchModal } from '@docsearch/modal';
import type { JSX } from 'react';
import '@docsearch/css/dist/style.css';
interface KeywordSearchProps {
appId: string;
apiKey: string;
indexName: string;
}
export function KeywordSearch({
appId,
apiKey,
indexName,
}: KeywordSearchProps): JSX.Element {
return (
<DocSearch appId={appId} apiKey={apiKey}>
<DocSearchButton
translations={{
buttonText: 'Search docs',
buttonAriaLabel: 'Search documentation',
}}
/>
<DocSearchModal indices={[indexName]} />
</DocSearch>
);
}
The provider opens the modal when a user selects the button, presses Ctrl/Command+K, or presses / outside an editable field. Closing the modal returns focus to DocSearchButton.
Add keyword search and Ask AI
Replace DocSearchModal with DocSearchAskAiModal. Create the assistant in Agent Studio, then configure its ID on the provider.
import { DocSearch } from '@docsearch/core';
import { DocSearchAskAiModal, DocSearchButton } from '@docsearch/modal';
import type { JSX } from 'react';
import '@docsearch/css/dist/style.css';
interface SearchWithAskAiProps {
appId: string;
apiKey: string;
indexName: string;
agentId: string;
}
export function SearchWithAskAi({
appId,
apiKey,
indexName,
agentId,
}: SearchWithAskAiProps): JSX.Element {
return (
<DocSearch appId={appId} apiKey={apiKey}>
<DocSearchButton />
<DocSearchAskAiModal
indices={[indexName]}
askAi={agentId}
/>
</DocSearch>
);
}
The askAi prop accepts an assistant ID string or an object with agentId. Use the object form when you need options such as indices, searchParameters, suggestedQuestions, promptSuggestions, tools, or memory. Set appId and apiKey on an individual modal or Sidepanel only when they must override the provider's defaults.
Understand the shared state
DocSearch holds one state value and shares it with its descendants:
| State | Meaning |
|---|---|
ready | No modal or Sidepanel is open. |
modal-search | The keyword search view is open. |
modal-askai | Ask AI is open in the modal. |
sidepanel | The Ask AI Sidepanel is open. |
The connected components manage these transitions for you:
DocSearchButtoncalls its ownonClickhandler, then opens keyword search.- Each modal registers itself with the provider and renders in a React portal only while a modal state is active.
DocSearchModalreads the provider's initial query and close action.DocSearchAskAiModalalso reads and updates the Ask AI state.
Use useDocSearch in a component under the provider when your application needs declarative access to this state.
import { useDocSearch } from '@docsearch/core';
import type { JSX } from 'react';
export function SearchControls(): JSX.Element {
const { closeModal, docsearchState, openModal, onAskAiToggle } =
useDocSearch();
return (
<div>
<span>Search state: {docsearchState}</span>
<button type="button" onClick={openModal}>
Open search
</button>
<button type="button" onClick={() => onAskAiToggle(true)}>
Open Ask AI
</button>
<button type="button" onClick={closeModal}>
Close search
</button>
</div>
);
}
Only call onAskAiToggle(true) when the provider contains DocSearchAskAiModal or a compatible Ask AI view.
Control DocSearch with a ref
Attach a DocSearchRef to the provider when non-React code or a parent component must control DocSearch.
import { DocSearch, type DocSearchRef } from '@docsearch/core';
import { DocSearchAskAiModal, DocSearchButton } from '@docsearch/modal';
import { useRef, type JSX } from 'react';
interface ControlledSearchProps {
appId: string;
apiKey: string;
indexName: string;
agentId: string;
onReady?: () => void;
onOpen?: () => void;
onClose?: () => void;
}
export function ControlledSearch(props: ControlledSearchProps): JSX.Element {
const searchRef = useRef<DocSearchRef>(null);
return (
<DocSearch
ref={searchRef}
onReady={props.onReady}
onOpen={props.onOpen}
onClose={props.onClose}
>
<DocSearchButton />
<button
type="button"
onClick={() =>
searchRef.current?.openAskAi({
query: 'How do I configure DocSearch?',
})
}
>
Ask a question
</button>
<DocSearchAskAiModal
appId={props.appId}
apiKey={props.apiKey}
indices={[props.indexName]}
askAi={props.agentId}
/>
</DocSearch>
);
}
The ref exposes these methods and read-only values. initialMessage has a required query and optional messageId and suggestedQuestionId fields.
The provider accepts onReady, onOpen, onClose, onSidepanelOpen, and onSidepanelClose. onReady runs after mount. The other callbacks run once when their corresponding view changes state, not every time React renders.
open
type: () => void
Opens keyword search.
close
type: () => void
Returns the provider to ready.
openAskAi
type: (initialMessage?: InitialAskAiMessage) => void
Opens Ask AI in a registered Sidepanel on desktop, or in the modal otherwise.
openSidepanel
type: (initialMessage?: InitialAskAiMessage) => void
Opens a registered Sidepanel. It does nothing when no Sidepanel is registered.
isReady
type: readonly boolean
Reports whether the provider is mounted.
isOpen
type: readonly boolean
Reports whether a modal view is open.
isSidepanelOpen
type: readonly boolean
Reports whether the Sidepanel is open.
isSidepanelSupported
type: readonly boolean
Reports whether desktop hybrid mode is available.
Load the modal on demand
Import the button eagerly and split the larger modal into another JavaScript chunk. The following Ask AI example preloads that chunk on hover, focus, or touch, then renders it only after the provider opens a modal state.
import { DocSearch, useDocSearch } from '@docsearch/core';
import { DocSearchButton } from '@docsearch/modal/button';
import type { DocSearchAskAiModalProps } from '@docsearch/modal/askai';
import { lazy, Suspense, type JSX } from 'react';
import '@docsearch/css/dist/style.css';
let modalImport: Promise<typeof import('@docsearch/modal/askai')> | undefined;
function loadModal(): Promise<typeof import('@docsearch/modal/askai')> {
modalImport ??= import('@docsearch/modal/askai');
return modalImport;
}
function preloadModal(): void {
void loadModal().catch(() => {
modalImport = undefined;
});
}
const LazyDocSearchAskAiModal = lazy(() =>
loadModal().then(({ DocSearchAskAiModal }) => ({
default: DocSearchAskAiModal,
}))
);
function ModalWhenOpen(props: DocSearchAskAiModalProps): JSX.Element | null {
const { isModalActive } = useDocSearch();
if (!isModalActive) {
return null;
}
return (
<Suspense fallback={<span role="status">Loading search...</span>}>
<LazyDocSearchAskAiModal {...props} />
</Suspense>
);
}
interface LazySearchProps {
appId: string;
apiKey: string;
indexName: string;
agentId: string;
}
export function LazySearch(props: LazySearchProps): JSX.Element {
return (
<DocSearch>
<DocSearchButton
onFocus={preloadModal}
onMouseEnter={preloadModal}
onTouchStart={preloadModal}
/>
<ModalWhenOpen
appId={props.appId}
apiKey={props.apiKey}
indices={[props.indexName]}
askAi={props.agentId}
/>
</DocSearch>
);
}
Render the provider and connected components only in the browser. The modal wrappers read document.body and window.scrollY when they render.
Use the exact entry points
Use the package root for convenience or a subpath to keep eager bundles focused.
| Import | Exports |
|---|---|
@docsearch/core | DocSearch, useDocSearch, their types, keyboard utilities, and theme utilities |
@docsearch/modal | DocSearchButton, DocSearchModal, DocSearchAskAiModal, and their prop types |
@docsearch/modal/button | DocSearchButton, DocSearchButtonProps |
@docsearch/modal/modal | DocSearchModal, DocSearchModalProps |
@docsearch/modal/askai | DocSearchAskAiModal, DocSearchAskAiModalProps |
The lower-level React entry points are @docsearch/react/button, @docsearch/react/modal, and @docsearch/react/askaiModal. They don't connect themselves to the composable provider. Use them only when you intend to manage portal rendering, refs, initialScrollY, close behavior, and Ask AI state yourself.
Load the styles
For either complete modal, import the combined stylesheet once:
import '@docsearch/css/dist/style.css';
The combined stylesheet contains variables, button styles, keyword modal styles, and Ask AI modal styles. It doesn't contain Sidepanel styles.
For a keyword-only CSS bundle, import the layers in this order:
import '@docsearch/css/dist/_variables.css';
import '@docsearch/css/dist/button.css';
import '@docsearch/css/dist/modal.css';
Add @docsearch/css/dist/_askai.css when you use DocSearchAskAiModal. Bundlers can also load the same files through @docsearch/react/style, @docsearch/react/style/button, @docsearch/react/style/modal, @docsearch/react/style/askai, and @docsearch/react/style/variables.
API summary
| API | Required configuration | Provider-managed behavior |
|---|---|---|
DocSearch | children | State, credentials defaults, theme, initial query, shortcuts, focus restoration, lifecycle callbacks, and DocSearchRef |
DocSearchButton | None | Button ref, theme, shortcuts, and opening keyword search |
DocSearchModal | At least one indices entry or deprecated indexName; appId and apiKey must be set here or on DocSearch | Open state, close action, initial scroll position, initial query, theme, and shortcuts |
DocSearchAskAiModal | The keyword modal configuration plus askAi | Keyword modal behavior, Ask AI state, Ask AI transitions, and hybrid detection |
useDocSearch | A parent DocSearch provider | Reads the context and throws when used outside the provider |
DocSearchButton accepts native React button props and translations. The connected wrapper doesn't accept theme or keyboardShortcuts; set those on DocSearch.
Both connected modal wrappers accept the corresponding low-level modal options, except the state and lifecycle fields supplied by the provider. Explicit appId and apiKey props override their respective provider values. Consult the modal API before adding options.