- Fix typos and grammar across 18 docs files (curly apostrophe in code sample, verb agreement, possessives, missing punctuation, etc.) - Capitalize proper nouns: JavaScript, DocSearch, Ask AI, LLM, Starlight - Fix code sample syntax: missing trailing comma in record-extractor, missing semicolon in sidepanel advanced-use-cases - Add missing Solution paragraph to AI-211 error entry in askai-errors - Remove trailing colons from section headings (api.mdx, api-reference.mdx) - Fix internal link in askai-markdown-indexing (.mdx extension removed) Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
428 lines
12 KiB
Text
428 lines
12 KiB
Text
---
|
|
id: examples
|
|
title: Examples and extensions
|
|
description: Live demos showing how to use and extend DocSearch beyond documentation-only use cases.
|
|
---
|
|
|
|
import { DocSearch } from '@docsearch/react';
|
|
import { DocSearch as DocSearchProvider } from '@docsearch/core';
|
|
import { DocSearchButton, DocSearchModal } from '@docsearch/modal';
|
|
import { DocSearchSidepanel } from '@docsearch/react/sidepanel';
|
|
import BrowserOnly from '@docusaurus/BrowserOnly';
|
|
import '@docsearch/css/dist/style.css';
|
|
import '@docsearch/css/dist/sidepanel.css';
|
|
|
|
> These examples are interactive. Click a button to open the modal and try a query.
|
|
|
|
## Basic keyword search
|
|
|
|
Use the default experience with your index credentials. This works great for typical docs, blogs, and any site with a DocSearch-compliant index.
|
|
|
|
```jsx
|
|
<DocSearch
|
|
appId="PMZUYBQDAK"
|
|
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
|
indexName="docsearch"
|
|
insights={true}
|
|
translations={{ button: { buttonText: 'keyword search (demo)' } }}
|
|
/>
|
|
```
|
|
|
|
<DocSearch
|
|
appId="PMZUYBQDAK"
|
|
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
|
indexName="docsearch"
|
|
insights={true}
|
|
translations={{ button: { buttonText: 'keyword search (demo)' } }}
|
|
/>
|
|
|
|
---
|
|
|
|
## Ask AI: ai-assisted answers
|
|
|
|
Add Algolia Ask AI to get synthesized answers grounded in your indexed content. You can scope the LLM context using `searchParameters` like `facetFilters`, `filters`, `attributesToRetrieve`,`restrictSearchableAttributes`, and `distinct`.
|
|
|
|
```jsx
|
|
<DocSearch
|
|
appId="PMZUYBQDAK"
|
|
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
|
indexName="docsearch"
|
|
askAi={{
|
|
assistantId: 'askAIDemo',
|
|
searchParameters: {
|
|
facetFilters: ['language:en'],
|
|
},
|
|
}}
|
|
insights={true}
|
|
translations={{ button: { buttonText: 'search with askai (demo)' } }}
|
|
/>
|
|
```
|
|
|
|
<DocSearch
|
|
appId="PMZUYBQDAK"
|
|
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
|
indexName="docsearch"
|
|
askAi={{
|
|
assistantId: 'askAIDemo',
|
|
searchParameters: {
|
|
facetFilters: ['language:en'],
|
|
},
|
|
}}
|
|
insights={true}
|
|
translations={{ button: { buttonText: 'search with askai (demo)' } }}
|
|
/>
|
|
|
|
---
|
|
|
|
## Sidepanel: persistent AI chat
|
|
|
|
The sidepanel provides a persistent chat interface anchored to the side of the page, ideal for documentation sites where users want to ask follow-up questions without losing their place. Look for the button on the bottom right of the screen to try the demo.
|
|
|
|
```jsx
|
|
<DocSearchSidepanel
|
|
appId="PMZUYBQDAK"
|
|
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
|
indexName="docsearch"
|
|
assistantId="askAIDemo"
|
|
/>
|
|
```
|
|
|
|
<BrowserOnly>
|
|
{() => (
|
|
<DocSearchSidepanel
|
|
appId="PMZUYBQDAK"
|
|
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
|
indexName="docsearch"
|
|
assistantId="askAIDemo"
|
|
/>
|
|
)}
|
|
</BrowserOnly>
|
|
|
|
---
|
|
|
|
## Composable API: DocSearchButton + DocSearchModal
|
|
|
|
Use the [Composable API](/docs/composable-api) to render the button and modal as separate components. This gives you explicit control over where each piece is rendered and when the modal code is loaded.
|
|
|
|
```jsx
|
|
import { DocSearch } from '@docsearch/core';
|
|
import { DocSearchButton, DocSearchModal } from '@docsearch/modal';
|
|
|
|
import '@docsearch/css/style.css';
|
|
|
|
<DocSearch>
|
|
<DocSearchButton translations={{ buttonText: 'Composable search (demo)' }} />
|
|
<DocSearchModal
|
|
appId="PMZUYBQDAK"
|
|
indexName="docsearch"
|
|
apiKey="a00716d83c64f6c61905c078b7d5ab66"
|
|
askAi={{
|
|
assistantId: 'ccdec697-e3fe-465b-a1c3-657e7bf18aef',
|
|
agentStudio: true,
|
|
}}
|
|
/>
|
|
</DocSearch>;
|
|
```
|
|
|
|
<BrowserOnly>
|
|
{() => (
|
|
<DocSearchProvider>
|
|
<DocSearchButton
|
|
translations={{ buttonText: 'Composable search (demo)' }}
|
|
/>
|
|
<DocSearchModal
|
|
appId="PMZUYBQDAK"
|
|
indexName="docsearch"
|
|
apiKey="a00716d83c64f6c61905c078b7d5ab66"
|
|
askAi={{
|
|
assistantId: 'ccdec697-e3fe-465b-a1c3-657e7bf18aef',
|
|
agentStudio: true,
|
|
}}
|
|
/>
|
|
</DocSearchProvider>
|
|
)}
|
|
</BrowserOnly>
|
|
|
|
---
|
|
|
|
## Custom hit rendering (`hitComponent`)
|
|
|
|
Replace the default hit markup to match your brand and layout. Below is a minimal example of a custom component.
|
|
|
|
```jsx
|
|
function CustomHit({ hit }) {
|
|
// render a compact, branded hit card
|
|
return (
|
|
<a
|
|
href={hit.url}
|
|
style={{ display: 'block', padding: '12px 16px', textDecoration: 'none' }}
|
|
>
|
|
<div style={{ display: 'flex', gap: 12 }}>
|
|
<div
|
|
style={{
|
|
width: 40,
|
|
height: 40,
|
|
backgroundColor: '#e3f2fd',
|
|
borderRadius: 6,
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
fontWeight: 600,
|
|
color: '#1976d2',
|
|
}}
|
|
>
|
|
{hit.type?.toUpperCase?.() || 'DOC'}
|
|
</div>
|
|
<div style={{ minWidth: 0 }}>
|
|
<div
|
|
style={{
|
|
fontWeight: 600,
|
|
whiteSpace: 'nowrap',
|
|
overflow: 'hidden',
|
|
textOverflow: 'ellipsis',
|
|
}}
|
|
>
|
|
{hit.hierarchy?.lvl1 || 'untitled'}
|
|
</div>
|
|
{hit.hierarchy?.lvl2 && (
|
|
<div
|
|
style={{
|
|
color: '#666',
|
|
whiteSpace: 'nowrap',
|
|
overflow: 'hidden',
|
|
textOverflow: 'ellipsis',
|
|
}}
|
|
>
|
|
{hit.hierarchy.lvl2}
|
|
</div>
|
|
)}
|
|
{hit.content && (
|
|
<div style={{ color: '#888', marginTop: 4 }}>{hit.content}</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</a>
|
|
);
|
|
}
|
|
|
|
<DocSearch
|
|
appId="PMZUYBQDAK"
|
|
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
|
indexName="docsearch"
|
|
hitComponent={CustomHit}
|
|
insights={true}
|
|
translations={{ button: { buttonText: 'custom hits (demo)' } }}
|
|
/>;
|
|
```
|
|
|
|
<DocSearch
|
|
appId="PMZUYBQDAK"
|
|
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
|
indexName="docsearch"
|
|
hitComponent={({ hit }) => {
|
|
// render a compact, branded hit card
|
|
return (
|
|
<a
|
|
href={hit.url}
|
|
style={{
|
|
display: 'block',
|
|
padding: '12px 16px',
|
|
textDecoration: 'none',
|
|
}}
|
|
>
|
|
<div style={{ display: 'flex', gap: 12 }}>
|
|
<div
|
|
style={{
|
|
width: 40,
|
|
height: 40,
|
|
backgroundColor: '#e3f2fd',
|
|
borderRadius: 6,
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
fontWeight: 600,
|
|
color: '#1976d2',
|
|
}}
|
|
>
|
|
{hit.type?.toUpperCase?.() || 'DOC'}
|
|
</div>
|
|
<div style={{ minWidth: 0 }}>
|
|
<div
|
|
style={{
|
|
fontWeight: 600,
|
|
whiteSpace: 'nowrap',
|
|
overflow: 'hidden',
|
|
textOverflow: 'ellipsis',
|
|
}}
|
|
>
|
|
{hit.hierarchy?.lvl1 || 'untitled'}
|
|
</div>
|
|
{hit.hierarchy?.lvl2 && (
|
|
<div
|
|
style={{
|
|
color: '#666',
|
|
whiteSpace: 'nowrap',
|
|
overflow: 'hidden',
|
|
textOverflow: 'ellipsis',
|
|
}}
|
|
>
|
|
{hit.hierarchy.lvl2}
|
|
</div>
|
|
)}
|
|
{hit.content && (
|
|
<div style={{ color: '#888', marginTop: 4 }}>{hit.content}</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</a>
|
|
);
|
|
}}
|
|
insights={true}
|
|
translations={{ button: { buttonText: 'custom hits (demo)' } }}
|
|
/>
|
|
|
|
---
|
|
|
|
## Opening links in new tabs
|
|
|
|
By default, DocSearch opens search result links in the current window. If you want results to open in new tabs, you need to use both a custom `hitComponent` and the `navigator` prop to handle both click and keyboard navigation consistently.
|
|
|
|
```jsx
|
|
// Custom hit component with target="_blank"
|
|
function HitWithNewTab({ hit, children }) {
|
|
return (
|
|
<a href={hit.url} target="_blank" rel="noopener noreferrer">
|
|
{children}
|
|
</a>
|
|
);
|
|
}
|
|
|
|
// Navigator configuration to handle keyboard navigation
|
|
const newTabNavigator = {
|
|
navigate: ({ itemUrl }) => window.open(itemUrl, '_blank'),
|
|
navigateNewTab: ({ itemUrl }) => window.open(itemUrl, '_blank'),
|
|
navigateNewWindow: ({ itemUrl }) => window.open(itemUrl, '_blank'),
|
|
};
|
|
|
|
<DocSearch
|
|
appId="PMZUYBQDAK"
|
|
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
|
indexName="docsearch"
|
|
hitComponent={HitWithNewTab}
|
|
navigator={newTabNavigator}
|
|
insights={true}
|
|
translations={{ button: { buttonText: 'open in new tabs (demo)' } }}
|
|
/>;
|
|
```
|
|
|
|
<DocSearch
|
|
appId="PMZUYBQDAK"
|
|
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
|
indexName="docsearch"
|
|
hitComponent={({ hit, children }) => (
|
|
<a href={hit.url} target="_blank" rel="noopener noreferrer">
|
|
{children}
|
|
</a>
|
|
)}
|
|
navigator={{
|
|
navigate: ({ itemUrl }) => window.open(itemUrl, '_blank'),
|
|
navigateNewTab: ({ itemUrl }) => window.open(itemUrl, '_blank'),
|
|
navigateNewWindow: ({ itemUrl }) => window.open(itemUrl, '_blank'),
|
|
}}
|
|
insights={true}
|
|
translations={{ button: { buttonText: 'open in new tabs (demo)' } }}
|
|
/>
|
|
|
|
<br></br>
|
|
|
|
:::warning
|
|
**Note**: Using only `hitComponent` with `target="_blank"` will work for mouse clicks, but keyboard navigation (arrows + Enter) requires the `navigator` prop to consistently open links in new tabs.
|
|
:::
|
|
|
|
---
|
|
|
|
## Bring-your-own-data shape with `transformItems`
|
|
|
|
DocSearch is not limited to DocSearch-like records. Use `transformItems` to adapt any record shape into the internal structure DocSearch expects. This lets you build search for apps, help centers, changelogs, or any custom content.
|
|
|
|
The snippet below maps a non-standard record to the internal format. Try it live:
|
|
|
|
```jsx
|
|
<DocSearch
|
|
appId="PMZUYBQDAK"
|
|
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
|
indexName="crawler_doc"
|
|
askAi={{ assistantId: 'askAIDemo' }}
|
|
searchParameters={{
|
|
attributesToRetrieve: ['*'],
|
|
attributesToSnippet: ['*'],
|
|
hitsPerPage: 20,
|
|
}}
|
|
transformItems={(items) =>
|
|
items.map((item) => ({
|
|
objectID: item.objectID,
|
|
content: item.content ?? '',
|
|
url: item.domain + item.path,
|
|
hierarchy: {
|
|
lvl0: (item.breadcrumb || []).join(' > ') ?? '',
|
|
lvl1: item.h1 ?? '',
|
|
lvl2: item.h2 ?? '',
|
|
lvl3: null,
|
|
lvl4: null,
|
|
lvl5: null,
|
|
lvl6: null,
|
|
},
|
|
url_without_anchor: item.domain + item.path,
|
|
type: 'content',
|
|
anchor: null,
|
|
_highlightResult: item._highlightResult,
|
|
_snippetResult: item._snippetResult,
|
|
}))
|
|
}
|
|
insights={true}
|
|
translations={{ button: { buttonText: 'transform items (demo)' } }}
|
|
/>
|
|
```
|
|
|
|
<DocSearch
|
|
appId="PMZUYBQDAK"
|
|
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
|
indexName="crawler_doc"
|
|
askAi={{ assistantId: 'askAIDemo' }}
|
|
searchParameters={{
|
|
attributesToRetrieve: ['*'],
|
|
attributesToSnippet: ['*'],
|
|
hitsPerPage: 20,
|
|
}}
|
|
transformItems={(items) =>
|
|
items.map((item) => ({
|
|
objectID: item.objectID,
|
|
content: item.content ?? '',
|
|
url: item.domain + item.path,
|
|
hierarchy: {
|
|
lvl0: (item.breadcrumb || []).join(' > ') ?? '',
|
|
lvl1: item.h1 ?? '',
|
|
lvl2: item.h2 ?? '',
|
|
lvl3: null,
|
|
lvl4: null,
|
|
lvl5: null,
|
|
lvl6: null,
|
|
},
|
|
url_without_anchor: item.domain + item.path,
|
|
type: 'content',
|
|
anchor: null,
|
|
_highlightResult: item._highlightResult,
|
|
_snippetResult: item._snippetResult,
|
|
}))
|
|
}
|
|
insights={true}
|
|
translations={{ button: { buttonText: 'transform items (demo)' } }}
|
|
/>
|
|
|
|
---
|
|
|
|
## Tips
|
|
|
|
- **Instrumentation**: enable `insights` to send usage analytics and iterate on relevance.
|
|
- **Ask AI scoping**: use `facetFilters`, `filters`, `attributesToRetrieve`, `restrictSearchableAttributes`, and `distinct` to control AI context and improve answer quality.
|
|
- **Customization**: use `hitComponent`, `transformItems`, and `translations` to make DocSearch feel native to any product surface.
|