Hybrid mode
Hybrid mode starts in the DocSearch modal, then moves an Ask AI request into the Sidepanel. Users can scan keyword results in a focused overlay and continue an AI conversation beside the page.
The handoff carries the prompt and its identifiers. It doesn't submit the same prompt in both interfaces.
Prerequisites
Before you add hybrid mode:
- Use matching v5 DocSearch package versions.
- Configure a searchable Algolia index and a public search-only API key.
- Create an assistant in Agent Studio.
- Decide whether you need React, JavaScript, or the DocSearch Docusaurus adapter.
Read the package references for core, modal, Sidepanel, JavaScript, Sidepanel JavaScript, and the Docusaurus adapter when you need options beyond this workflow.
Understand the handoff
An Ask AI action can come from a typed prompt, a suggested question, a prompt suggestion in keyword results, or a saved conversation.
DocSearch represents the action as an InitialAskAiMessage:
interface InitialAskAiMessage {
query: string;
messageId?: string;
suggestedQuestionId?: string;
}
Preserve the complete object during a manual handoff:
querybecomes the first prompt when the Sidepanel starts a conversation.suggestedQuestionIdis sent with the prompt so Agent Studio can identify the selected suggestion.messageIdlets the Sidepanel restore a locally stored conversation. If it can't find that conversation, it starts a new one withquery.
Add hybrid mode
- React
- JavaScript
React
Install the connected React packages and CSS:
- npm
- Yarn
- pnpm
- Bun
npm install @docsearch/core@^5 @docsearch/modal@^5 @docsearch/sidepanel@^5 @docsearch/css@^5
yarn add @docsearch/core@^5 @docsearch/modal@^5 @docsearch/sidepanel@^5 @docsearch/css@^5
pnpm add @docsearch/core@^5 @docsearch/modal@^5 @docsearch/sidepanel@^5 @docsearch/css@^5
bun add @docsearch/core@^5 @docsearch/modal@^5 @docsearch/sidepanel@^5 @docsearch/css@^5
Render DocSearchAskAiModal and Sidepanel under the same DocSearch provider. The Sidepanel registers itself with the provider after mount. From then on, desktop Ask AI actions move to the Sidepanel automatically.
import { DocSearch } from '@docsearch/core';
import { DocSearchAskAiModal, DocSearchButton } from '@docsearch/modal';
import { Sidepanel, SidepanelButton } from '@docsearch/sidepanel';
import type { JSX } from 'react';
import '@docsearch/css/dist/style.css';
import '@docsearch/css/dist/sidepanel.css';
interface HybridSearchProps {
appId: string;
apiKey: string;
keywordIndexName: string;
askAiIndexName: string;
assistantId: string;
onReady?: () => void;
onOpen?: () => void;
onClose?: () => void;
onSidepanelOpen?: () => void;
onSidepanelClose?: () => void;
}
export function HybridSearch({
appId,
apiKey,
keywordIndexName,
askAiIndexName,
assistantId,
onReady,
onOpen,
onClose,
onSidepanelOpen,
onSidepanelClose,
}: HybridSearchProps): JSX.Element {
return (
<DocSearch
onReady={onReady}
onOpen={onOpen}
onClose={onClose}
onSidepanelOpen={onSidepanelOpen}
onSidepanelClose={onSidepanelClose}
>
<DocSearchButton />
<DocSearchAskAiModal
appId={appId}
apiKey={apiKey}
indices={[keywordIndexName]}
askAi={{ assistantId }}
/>
<SidepanelButton />
<Sidepanel
appId={appId}
apiKey={apiKey}
indexName={askAiIndexName}
assistantId={assistantId}
/>
</DocSearch>
);
}
SidepanelButton gives users a direct Ask AI entry point. You can omit it without disabling hybrid handoff. Keep Sidepanel mounted so it can register before a user submits an Ask AI request.
Use the same index name for keywordIndexName and askAiIndexName when one index serves both experiences. Pass different names when your Agent Studio setup uses a dedicated content index.
React lifecycle callbacks
Put lifecycle callbacks on DocSearch so they observe the shared state.
During the automatic React handoff, the state changes directly from modal-askai to sidepanel. That transition calls onSidepanelOpen; it doesn't call onClose.
onReady
type: () => void| optional
Runs when the provider mounts.
onOpen
type: () => void| optional
Runs when a modal opens from ready or sidepanel.
onClose
type: () => void| optional
Runs when a modal changes directly to ready.
onSidepanelOpen
type: () => void| optional
Runs when the state changes to sidepanel.
onSidepanelClose
type: () => void| optional
Runs when the state leaves sidepanel.
JavaScript
The JavaScript packages create independent DocSearch and Sidepanel instances. Connect them with interceptAskAiEvent.
- npm
- Yarn
- pnpm
- Bun
npm install @docsearch/js@^5 @docsearch/sidepanel-js@^5 @docsearch/css@^5
yarn add @docsearch/js@^5 @docsearch/sidepanel-js@^5 @docsearch/css@^5
pnpm add @docsearch/js@^5 @docsearch/sidepanel-js@^5 @docsearch/css@^5
bun add @docsearch/js@^5 @docsearch/sidepanel-js@^5 @docsearch/css@^5
Add one mount element for each instance:
<div id="docsearch"></div>
<div id="docsearch-sidepanel"></div>
Create the Sidepanel first so the interceptor always has a target. The mobile check in this example matches the React integration: mobile users stay in the Ask AI modal, while desktop users move to the Sidepanel.
import docsearch, { type DocSearchInstance } from '@docsearch/js';
import sidepanel, { type SidepanelInstance } from '@docsearch/sidepanel-js';
import '@docsearch/css/dist/style.css';
import '@docsearch/css/dist/sidepanel.css';
const appId = 'YOUR_APP_ID';
const apiKey = 'YOUR_SEARCH_API_KEY';
const keywordIndexName = 'YOUR_KEYWORD_INDEX_NAME';
const askAiIndexName = 'YOUR_ASK_AI_INDEX_NAME';
const assistantId = 'YOUR_ASSISTANT_ID';
const mobileQuery = window.matchMedia('(max-width: 768px)');
let search: DocSearchInstance | undefined;
let panel: SidepanelInstance | undefined;
panel = sidepanel({
container: '#docsearch-sidepanel',
appId,
apiKey,
indexName: askAiIndexName,
assistantId,
onReady: () => {
document.body.setAttribute('data-sidepanel-ready', '');
},
onOpen: () => {
search?.close();
document.body.setAttribute('data-sidepanel-open', '');
},
onClose: () => {
document.body.removeAttribute('data-sidepanel-open');
},
});
search = docsearch({
container: '#docsearch',
appId,
apiKey,
indices: [keywordIndexName],
askAi: { assistantId },
interceptAskAiEvent: (initialMessage) => {
if (mobileQuery.matches) {
return false;
}
search?.close();
panel?.open(initialMessage);
return true;
},
onReady: () => {
document.body.setAttribute('data-docsearch-ready', '');
},
onOpen: () => {
panel?.close();
document.body.setAttribute('data-search-open', '');
},
onClose: () => {
document.body.removeAttribute('data-search-open');
},
});
The root @docsearch/js export includes Ask AI. The keyword-only JavaScript entry point is @docsearch/js/docsearch; don't use that entry point for hybrid mode because it has no Ask AI action to intercept.
How interceptAskAiEvent works
interceptAskAiEvent runs before the modal changes to Ask AI or sends a message.
- Return
trueafter another interface accepts the request. DocSearch clears the keyword query and skips all default Ask AI behavior. - Return
falseorundefinedto keep the default Ask AI modal flow. - Pass
initialMessageunchanged tosidepanelInstance.open(initialMessage)so conversation and suggestion IDs survive.
Keep the onOpen callbacks that close the other instance. They prevent two interfaces from staying open when application code calls search.open(), search.openAskAi(), or panel.open() directly.
The JavaScript instances expose these lifecycle callbacks and controls:
- The DocSearch instance accepts the
onReady,onOpen, andonClosecallbacks, and exposesopen(),close(),openAskAi(),destroy(),isReady, andisOpen. - The Sidepanel instance accepts the
onReady,onOpen, andonClosecallbacks, and exposesopen(),close(),destroy(),isReady, andisOpen.
Call both destroy() methods if your application removes the mount elements or tears down the page without a full navigation.
Add hybrid mode to Docusaurus
Hybrid mode is supported through @docsearch/docusaurus-adapter. The adapter loads the v5 modal and Sidepanel, connects their state, and applies the mobile behavior described in this guide.
Install the adapter and keep @docusaurus/preset-classic:
- npm
- Yarn
- pnpm
- Bun
npm install @docsearch/docusaurus-adapter@^5
yarn add @docsearch/docusaurus-adapter@^5
pnpm add @docsearch/docusaurus-adapter@^5
bun add @docsearch/docusaurus-adapter@^5
Configure DocSearch under themeConfig.docsearch. Set top-level sidePanel to true or to a Sidepanel options object.
export default {
plugins: ['@docsearch/docusaurus-adapter'],
themeConfig: {
docsearch: {
appId: 'YOUR_APP_ID',
apiKey: 'YOUR_SEARCH_API_KEY',
indices: [{ name: 'YOUR_INDEX_NAME' }],
askAi: {
assistantId: 'YOUR_ASSISTANT_ID',
},
sidePanel: true,
contextualSearch: true,
searchPage: { path: 'search' },
},
},
};
The adapter requires askAi to be an object with assistantId; it doesn't accept the string shorthand in Docusaurus configuration. It also requires askAi whenever sidePanel is set.
Don't put sidePanel inside askAi. Don't configure this adapter under themeConfig.algolia. If you need function-valued custom tools, pass them through a swizzled @theme/SearchBar component because Docusaurus removes functions while serializing theme configuration.
The built-in @docusaurus/theme-search-algolia integration doesn't provide this v5 Sidepanel configuration. Use the DocSearch adapter for this workflow.
Mobile behavior
The React provider treats viewports that match (max-width: 768px) as mobile. On those viewports, an Ask AI action stays in the modal even when a Sidepanel is registered. This avoids moving the conversation into the desktop Sidepanel layout.
The provider updates this decision on window resize. The Sidepanel also avoids focusing its prompt automatically on mobile because opening the virtual keyboard can disrupt the layout.
The JavaScript packages don't share a provider, so they don't apply the hybrid mobile decision to your interceptor. Add the matchMedia branch shown in the JavaScript example when you want the same behavior.
Load the styles
Hybrid mode needs both style bundles:
import '@docsearch/css/dist/style.css';
import '@docsearch/css/dist/sidepanel.css';
style.css includes shared variables, the button, keyword modal, and Ask AI modal. sidepanel.css adds the Sidepanel layout and imports the shared variables it needs.
If your bundler resolves the React style entry points, @docsearch/react/style and @docsearch/react/style/sidepanel load the same bundles. Import each bundle once.
Troubleshoot hybrid mode
Ask AI stays in the modal on desktop
Confirm that Sidepanel is mounted under the same DocSearch provider before the Ask AI action. The provider enables automatic handoff only after the Sidepanel registers. Also confirm that the viewport is wider than 768 pixels.
Both the modal and Sidepanel submit the prompt
Return true from interceptAskAiEvent after calling panel.open(initialMessage). Returning false or no value tells the modal to continue its default flow.
Mobile users open the Sidepanel
In JavaScript integrations, check (max-width: 768px) in the interceptor and return false on a match. React and the Docusaurus adapter already make this decision.
The Sidepanel loses a suggestion or saved conversation
Pass the complete initialMessage object to open(). Rebuilding it with only query discards suggestedQuestionId and messageId.
A callback doesn't run during React handoff
Expect onSidepanelOpen, not onClose, when React changes directly from the modal to the Sidepanel. Use the shared provider callbacks rather than treating modal close as the handoff signal.
Two JavaScript interfaces remain open
Close the search instance in the Sidepanel's onOpen, and close the Sidepanel in the search instance's onOpen. This also covers programmatic calls that bypass the interceptor.
The interface is unstyled
Import both style.css and sidepanel.css. Check that your bundler includes CSS imports from dependencies.
Docusaurus rejects the configuration
Use themeConfig.docsearch, pass askAi as an object, and put sidePanel beside askAi. Install @docsearch/docusaurus-adapter@^5 instead of configuring hybrid mode through the built-in Algolia theme.
Server rendering fails with window or document errors
Mount the composable React components only in the browser. The Docusaurus adapter handles this boundary and lazy-loads the modal and Sidepanel client-side.