Skip to main content
Version: Stable (v5.x)

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.

ComponentUse it for
DocSearchModalKeyword search without Ask AI
DocSearchAskAiModalKeyword 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:

Install the packages

Install matching v5 versions of the DocSearch packages:

npm install @docsearch/core@^5.0.0-beta @docsearch/modal@^5.0.0-beta @docsearch/css@^5.0.0-beta

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.

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

SearchWithAskAi.tsx
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:

StateMeaning
readyNo modal or Sidepanel is open.
modal-searchThe keyword search view is open.
modal-askaiAsk AI is open in the modal.
sidepanelThe Ask AI Sidepanel is open.

The connected components manage these transitions for you:

  • DocSearchButton calls its own onClick handler, then opens keyword search.
  • Each modal registers itself with the provider and renders in a React portal only while a modal state is active.
  • DocSearchModal reads the provider's initial query and close action.
  • DocSearchAskAiModal also 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.

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

ImportExports
@docsearch/coreDocSearch, useDocSearch, their types, keyboard utilities, and theme utilities
@docsearch/modalDocSearchButton, DocSearchModal, DocSearchAskAiModal, and their prop types
@docsearch/modal/buttonDocSearchButton, DocSearchButtonProps
@docsearch/modal/modalDocSearchModal, DocSearchModalProps
@docsearch/modal/askaiDocSearchAskAiModal, 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

APIRequired configurationProvider-managed behavior
DocSearchchildrenState, credentials defaults, theme, initial query, shortcuts, focus restoration, lifecycle callbacks, and DocSearchRef
DocSearchButtonNoneButton ref, theme, shortcuts, and opening keyword search
DocSearchModalAt least one indices entry or deprecated indexName; appId and apiKey must be set here or on DocSearchOpen state, close action, initial scroll position, initial query, theme, and shortcuts
DocSearchAskAiModalThe keyword modal configuration plus askAiKeyword modal behavior, Ask AI state, Ask AI transitions, and hybrid detection
useDocSearchA parent DocSearch providerReads 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.