React SDK
The @yak-io/react package provides React components and hooks for integrating Yak into any React application.
For Next.js applications, use @yak-io/nextjs instead for automatic route scanning and App Router integration.
Installation
Works with React 17, 18, and 19.
npm install @yak-io/react @yak-io/javascriptpnpm add @yak-io/react @yak-io/javascriptyarn add @yak-io/react @yak-io/javascriptbun add @yak-io/react @yak-io/javascriptQuick Start
Add the Provider and Widget
// App.tsx
import { YakProvider, YakWidget } from "@yak-io/react";
export default function App() {
return (
<YakProvider
appId="your-app-id"
getConfig={async () => {
const res = await fetch("/api/yak");
return res.json();
}}
onToolCall={async (name, args) => {
const res = await fetch("/api/yak", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name, args }),
});
const data = await res.json();
if (!data.ok) throw new Error(data.error);
return data.result;
}}
>
{/* Your app content */}
<YakWidget />
</YakProvider>
);
}Set up server handlers
Use @yak-io/javascript to create the API endpoints on your backend. See the JavaScript SDK for runtime-specific examples (Express, Hono, Cloudflare Workers, etc.).
// api/yak.ts
import { createYakHandler } from "@yak-io/javascript/server";
export const { GET, POST } = createYakHandler({
routes: [
{ path: "/", title: "Home" },
{ path: "/products", title: "Products" },
],
});Components
YakProvider
Wraps your application and manages widget state and communication.
| Prop | Type | Required | Description |
|---|---|---|---|
appId | string | Yes | Your Yak application ID |
mode | "chat" | "voice" | "both" | No | Which surfaces the trigger exposes. Defaults to "chat". See Voice Mode. |
getConfig | () => Promise<ChatConfig> | No | Config provider for routes and tools (used by chat and voice) |
onToolCall | (name, args) => Promise<unknown> | No | Handler for tool execution (used by chat and voice) |
theme | Theme | No | Widget styling options |
onRedirect | (path: string) => void | No | Custom navigation handler |
disableRestartButton | boolean | No | Hide the restart button in the header |
disablePageContent | boolean | No | Stop sending any page context (URL, title, and visible text) to the assistant. The widget still works, but it won't be aware of the page the user is on. |
user | { id, hash } | No | Signed end-user identity. Enables server-side conversation persistence and the history pane. |
Privacy: By default Yak shares the current page's URL, title, and visible text with the assistant so it can answer questions about the page the user is viewing. Set disablePageContent to turn this off entirely — the SDK then sends nothing about the page (not even the URL), so the assistant can't answer page-specific questions.
YakWidget
Renders the trigger pill — logo plus one or two icon buttons depending on mode.
| Prop | Type | Default | Description |
|---|---|---|---|
mode | "chat" | "voice" | "both" | inherited from provider | Override the provider mode for this trigger |
position | WidgetPosition | "bottom-right" | One of nine positions (e.g. "bottom-left", "top-center") |
colorMode | "light" | "dark" | "system" | — | Force a color mode for the pill |
lightButton | { background?, color?, border? } | — | Custom pill colors in light mode |
darkButton | { background?, color?, border? } | — | Custom pill colors in dark mode |
Hooks
useYak
Access widget controls from any component inside YakProvider:
import { useYak } from "@yak-io/react";
export function ChatButton() {
const { open, close, openWithPrompt, isOpen } = useYak();
return (
<div>
<button onClick={() => open()}>Open Chat</button>
<button onClick={() => openWithPrompt("Help me with this page")}>
Get Help
</button>
{isOpen && <button onClick={() => close()}>Close</button>}
</div>
);
}| Property | Type | Description |
|---|---|---|
mode | "chat" | "voice" | "both" | The current mode the provider was configured with |
open | () => void | Open the chat panel |
close | () => void | Close the chat panel |
openWithPrompt | (prompt: string) => void | Open and send a specific prompt |
isOpen | boolean | Whether the chat panel is currently open |
isReady | boolean | Whether the chat iframe has loaded and can receive messages |
chatLoading | boolean | isOpen && !isReady — the panel is opening but not yet interactive |
voiceState | VoiceState | "idle" | "connecting" | "listening" | "thinking" | "speaking" | "error" |
voiceIsActive | boolean | true while a voice session is live |
voiceLoading | boolean | true while the voice session is connecting |
voiceStart() | () => Promise<void> | Start a voice session (call from a user gesture) |
voiceStop() | () => Promise<void> | Stop the current voice session |
voiceToggle() | () => Promise<void> | Start if idle/error, stop if active |
See Voice Mode for the full voice API and behavior.
useYakToolEvent
Subscribe to tool call completion events — useful for keeping your UI in sync with agent actions:
import { useYakToolEvent } from "@yak-io/react";
function OrderPage({ orderId }: { orderId: string }) {
const queryClient = useQueryClient();
useYakToolEvent((event) => {
if (event.ok && event.name.startsWith("order.")) {
queryClient.invalidateQueries({ queryKey: ["order", orderId] });
}
});
return <OrderDetails orderId={orderId} />;
}See UI Synchronization for more details.
Router Integration
Pass your framework's navigation function to onRedirect for client-side navigation:
// React Router
import { useNavigate } from "react-router-dom";
function App() {
const navigate = useNavigate();
return (
<YakProvider onRedirect={(path) => navigate(path)} {...props}>
{children}
</YakProvider>
);
}
// TanStack Router
<YakProvider onRedirect={(path) => router.navigate({ to: path })} />Next Steps
- Voice Mode — Add a voice icon to the trigger pill
- Styling — Customize appearance, position, and colors
- Programmatic Control — Open widget from code, context-sensitive help
- UI Synchronization — Keep your UI in sync with agent actions
- Tool Adapters — Connect your APIs as tools