1
0
Fork 0
docsearch/packages/website/docs/composable-api.mdx
Paul Jankowski 596397c359
feat(docs): Document v5 beta (#2935)
* chore(docs): v5 documentation

* Writing style clean up

* fix: website after conflicts
2026-07-30 09:47:28 -04:00

412 lines
13 KiB
Text

---
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:
<Tabs groupId="package-manager" aria-label="Package manager">
<TabItem value="npm" label="npm">
```bash
npm install @docsearch/core@^5.0.0-beta @docsearch/modal@^5.0.0-beta @docsearch/css@^5.0.0-beta
```
</TabItem>
<TabItem value="yarn" label="Yarn">
```bash
yarn add @docsearch/core@^5.0.0-beta @docsearch/modal@^5.0.0-beta @docsearch/css@^5.0.0-beta
```
</TabItem>
<TabItem value="pnpm" label="pnpm">
```bash
pnpm add @docsearch/core@^5.0.0-beta @docsearch/modal@^5.0.0-beta @docsearch/css@^5.0.0-beta
```
</TabItem>
<TabItem value="bun" label="Bun">
```bash
bun add @docsearch/core@^5.0.0-beta @docsearch/modal@^5.0.0-beta @docsearch/css@^5.0.0-beta
```
</TabItem>
</Tabs>
## Add keyword search
Wrap the button and keyword modal in one `DocSearch` provider. Pass a public search-only API key. Prefer `indices` over the deprecated `indexName` and `searchParameters` props.
```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 (
<DocSearch>
<DocSearchButton
translations={{
buttonText: 'Search docs',
buttonAriaLabel: 'Search documentation',
}}
/>
<DocSearchModal appId={appId} apiKey={apiKey} indices={[indexName]} />
</DocSearch>
);
}
```
The provider opens the modal when a user selects the button, presses <kbd>Ctrl</kbd>/<kbd>Command</kbd>+<kbd>K</kbd>, or presses <kbd>/</kbd> 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 pass its ID through `askAi`.
```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;
assistantId: string;
}
export function SearchWithAskAi({
appId,
apiKey,
indexName,
assistantId,
}: SearchWithAskAiProps): JSX.Element {
return (
<DocSearch>
<DocSearchButton />
<DocSearchAskAiModal
appId={appId}
apiKey={apiKey}
indices={[indexName]}
askAi={{ assistantId }}
/>
</DocSearch>
);
}
```
The `askAi` prop also accepts an assistant ID string. Use the object form when you need options such as `indices`, `searchParameters`, `suggestedQuestions`, `promptSuggestions`, `tools`, or `memory`. See the [React package reference](/docs/packages/react/api-reference) for those option types.
## 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 (
<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.
```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;
assistantId: 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={{ assistantId: props.assistantId }}
/>
</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.
```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<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;
assistantId: 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={{ assistantId: props.assistantId }}
/>
</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:
```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, theme, initial query, shortcuts, focus restoration, lifecycle callbacks, and `DocSearchRef` |
| `DocSearchButton` | None | Button ref, theme, shortcuts, and opening keyword search |
| `DocSearchModal` | `appId`, `apiKey`, and at least one `indices` entry or deprecated `indexName` | 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. Consult the [modal API](/docs/packages/modal/api) before adding options.