---
title: Composable API
description: Build a DocSearch v5 interface from the button, keyword modal, Ask AI modal, and shared state provider.
---
import TabItem from '@theme/TabItem';
import Tabs from '@theme/Tabs';
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](/docs/packages/modal/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/core`](/docs/packages/core/overview) provides `DocSearch`, shared state, keyboard handling, and the imperative ref.
- [`@docsearch/modal`](/docs/packages/modal/overview) provides the provider-connected button and modal components.
- [`@docsearch/css`](/docs/packages/css/styling) provides the styles.
## Install the packages
Install matching v5 versions of the DocSearch packages:
```bash
npm install @docsearch/core@^5.0.0-beta @docsearch/modal@^5.0.0-beta @docsearch/css@^5.0.0-beta
```
```bash
yarn add @docsearch/core@^5.0.0-beta @docsearch/modal@^5.0.0-beta @docsearch/css@^5.0.0-beta
```
```bash
pnpm add @docsearch/core@^5.0.0-beta @docsearch/modal@^5.0.0-beta @docsearch/css@^5.0.0-beta
```
```bash
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.
```tsx title="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 (
);
}
```
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](/docs/agent-studio/getting-started), then configure its ID on the provider.
```tsx title="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 (
);
}
```
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:
- `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.
```tsx
import { useDocSearch } from '@docsearch/core';
import type { JSX } from 'react';
export function SearchControls(): JSX.Element {
const { closeModal, docsearchState, openModal, onAskAiToggle } =
useDocSearch();
return (
Search state: {docsearchState}
);
}
```
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.
```tsx
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(null);
return (
);
}
```
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.
```tsx title="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 | undefined;
function loadModal(): Promise {
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 (
Loading search...}>
);
}
interface LazySearchProps {
appId: string;
apiKey: string;
indexName: string;
agentId: string;
}
export function LazySearch(props: LazySearchProps): JSX.Element {
return (
);
}
```
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:
```ts
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:
```ts
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](/docs/packages/modal/api) before adding options.