AI Search React/TSX Implementation
Log in to add to favouritesPage last updated 05 October 2026
This guide walks through integrating the insytful-ai-search-components widgets into an existing React application, from install to production-ready configuration.
1. Install
npm install insytful-ai-search-componentsThe package needs react and react-dom 17 or later, which your project should already have.
Import the stylesheet once, near your app's entry point:
import "insytful-ai-search-components/style.css"
2. Add the search
import { Theme, InsytfulSearch } from "insytful-ai-search-components";
export function SiteSearch() {
return (
<Theme>
<InsytfulSearch.Root
options={{ config: "your-config", baseUrl: "https://api.example.com" }}
>
<InsytfulSearch.Trigger>Search</InsytfulSearch.Trigger>
<InsytfulSearch.Portal>
<InsytfulSearch.Close />
<InsytfulSearch.Title>How can we help?</InsytfulSearch.Title>
<InsytfulSearch.Description>
Ask a question and get an AI-generated answer.
</InsytfulSearch.Description>
<InsytfulSearch.Input placeholder="Ask a question..." />
<InsytfulSearch.Messages />
<InsytfulSearch.Suggestions
items={["How do I get started?", "What does this product do?"]}
/>
<InsytfulSearch.Disclaimer>
AI-generated answers may be inaccurate.
</InsytfulSearch.Disclaimer>
</InsytfulSearch.Portal>
</InsytfulSearch.Root>
</Theme>
);
}The Trigger button renders where you place it, for example in your header. The dialog in Portal opens on top of the page, so your layout and z-index don't affect it.
Only Root, Portal, Input and Messages are required. Everything else is optional.
<Theme> gives the components their default look. Without it they render unstyled, for you to style yourself (see Theming).
3. Configuration
options on Root needs two values from your account manager:
options={{
config: "your-config", // your Insytful config ID
baseUrl: "https://api.example.com", // your AI Search API URL
}}On public sites you can add a reCAPTCHA v3 site key to help block automated abuse:
options={{ config: "…", baseUrl: "…", recaptchaSiteKey: "your-site-key" }}reCAPTCHA v3 runs in the background, so visitors never see a challenge. Each question is sent with a token, and the AI Search API checks it.
4. Show answers as formatted text
Answers arrive as markdown. To show headings, lists and links, pass a markdown renderer to Root:
import ReactMarkdown from "react-markdown";
<InsytfulSearch.Root
options={…}
renderMarkdown={(content) => <ReactMarkdown>{content}</ReactMarkdown>}
>Without it, answers show as plain text. The package doesn't include a renderer, so you choose one. react-markdown is a safe default because it doesn't render raw HTML unless you turn that on.
5. Open from your own button
To open the search from somewhere else, such as a keyboard shortcut or a link in your navigation, control the open state yourself:
const [open, setOpen] = useState(false);
<button onClick={() => setOpen(true)}>Ask AI</button>
<InsytfulSearch.Root open={open} onOpenChange={setOpen} options={…}>
<InsytfulSearch.Portal>…</InsytfulSearch.Portal>
</InsytfulSearch.Root>Trigger is optional when you do this. Closing the dialog keeps the conversation; reloading the page starts a new one.
Sticky headers
If your site has a sticky header, mark it so the dialog opens below it:
<header data-insytful-offset>…</header>
You can mark more than one element, such as a header and a cookie banner, and their heights are added together. The dialog follows any height changes automatically.
6. Handling errors
Show an error message by reading error from the search context:
function ErrorArea() {
const { error } = InsytfulSearch.useSearchContext("ErrorArea");
return error ? (
<InsytfulSearch.ErrorCallout
title="We couldn't get an answer"
cta={{ text: "Contact us", path: "/contact" }}
/>
) : null;
}Place <ErrorArea /> inside Portal, below Messages. title, text and cta are all optional and have sensible defaults.
7. Add a classic search fallback
If you also have a traditional search results page, let visitors switch between AI answers and your results:
<InsytfulSearch.Modes defaultValue="ai">
<InsytfulSearch.ModeSwitch>
{({ mode, onSwitch }) => (
<div role="group" aria-label="Search mode">
<button aria-pressed={mode === "ai"} onClick={() => onSwitch("ai")}>AI answer</button>
<button aria-pressed={mode === "classic"} onClick={() => onSwitch("classic")}>Search results</button>
</div>
)}
</InsytfulSearch.ModeSwitch>
<InsytfulSearch.Mode name="ai">
<InsytfulSearch.Input />
<InsytfulSearch.Messages />
</InsytfulSearch.Mode>
<InsytfulSearch.Mode name="classic" path="/search?q=">
<InsytfulSearch.Input />
</InsytfulSearch.Mode>
</InsytfulSearch.Modes>In classic mode, submitting a question goes to path with the question added to the end, so path must end with the query parameter and =. To use your app's router instead of a full page load, pass onNavigate={(url) => navigate(url)}.
8. Show an answer above your search results
Search.Overview shows an AI answer inline, with no dialog. Put it above your existing results and pass it the visitor's search:
<Theme>
<InsytfulSearch.Overview
options={{ config: "your-config", baseUrl: "https://api.example.com" }}
term={query}
renderMarkdown={(content) => <ReactMarkdown>{content}</ReactMarkdown>}
/>
</Theme>The answer starts as soon as term is set. Long answers are shortened behind a "Show more" button. For a follow-up conversation in place of a single answer, add type="conversational".
renderMarkdown is required here: without it the answer text doesn't appear.
9. Theming
Override the design tokens on .insytful-theme in your own stylesheet:
.insytful-theme {
--insytful-brand-primary: #5128c3;
--insytful-font-family: "Helvetica Neue", Arial, sans-serif;
}The dialog renders in a Shadow DOM, which your stylesheet can't reach. To style it, pass the CSS to <Theme> instead:
<Theme css={`.insytful-theme { --insytful-brand-primary: #5128c3; }`}>You don't need !important. See AI Search theming for the common tokens, and the full reference for all of them.
10. Collect answer feedback
Add a report link and helpful / unhelpful buttons under each answer:
<InsytfulSearch.Messages
feedback={{
report: { text: "Report an error", href: "/report" },
onVote: (vote, { mid }) => analytics.track("ai_vote", { vote, mid }),
}}
/>Votes are sent to Insytful automatically. Use onVote only if you want to record them in your own analytics too. Search.Overview takes the same feedback prop.
11. Quick-action buttons
Answers can come with quick-action buttons (call, email, link or a custom event), which are set up in the CMS. Call, email and link buttons work without any setup.
An event button sends a named event that you listen for on the page. Use it to open a web chat or a booking form, for example:
<script>
(window.insytfulAISearchEvents ??= new EventTarget())
.addEventListener("openWebChat", (e) => {
// e.detail has the button's configured data
});
</script>To track every click, pass onCtaClick to Root.
12. Build without a backend
Add isDevMode to Root and the search returns a sample answer instead of calling the API:
<InsytfulSearch.Root
options={…}
isDevMode={process.env.NODE_ENV === "development"}
>The sample answer uses every markdown feature and includes one quick-action button of each type, which makes it useful for testing your styles. It waits 8 seconds before the first words appear so you can see the loading state.
Make sure dev mode is off in production. Tie it to an environment check, as above, rather than setting it to true.
Building your own UI
If neither the dialog nor Overview fits, the underlying hooks (useRAGResponse, useRAGConversation) are exported so you can build your own. See the full reference.