# Cossistant documentation
URL: /docs
Quickstart with your framework [#quickstart-with-your-framework]
Start by selecting your framework of choice, then follow the instructions to install the dependencies and structure your app. Cossistant currently ships quickstarts for Next.js and React.
Next.js
Add Cossistant
{``}
widget to your Next.js project.
React
Add Cossistant
{``}
widget to any React project.
# Visitors
URL: /docs/concepts
What are Visitors? [#what-are-visitors]
**Visitors** are automatically created when someone loads your application with the Cossistant SDK. They represent anonymous users before they're identified as [contacts](/docs/concepts/contacts).
Every visitor is unique per device/browser, persisting across page loads and sessions.
How Visitors are Tracked [#how-visitors-are-tracked]
Cossistant uses a combination of techniques to maintain visitor identity:
* **LocalStorage**: Stores a unique visitor ID in the browser
* **Fingerprinting**: Browser and device characteristics for additional persistence
* **Automatic creation**: No setup required—visitors are created on first load
This means a visitor on desktop and the same person on mobile will be two different visitors until they're [identified](/docs/concepts/contacts).
Anonymous by Default [#anonymous-by-default]
Visitors start anonymous with no personal information:
* No name, email, or external ID
* Only browser-derived data (language, timezone)
* Can start [conversations](/docs/concepts/conversations) without authentication
* Perfect for public-facing pages or logged-out users
Visitor Properties [#visitor-properties]
Each visitor has:
* **id**: Unique identifier for this visitor
* **language**: Browser language (e.g., "en-US")
* **timezone**: Browser timezone (e.g., "America/New\_York")
* **isBlocked**: Whether this visitor has been blocked from support
* **contact**: The associated contact (null until identified)
Identifying Visitors [#identifying-visitors]
Transform anonymous visitors into identified [contacts](/docs/concepts/contacts) when users authenticate:
Using the Component (Server Components) [#using-the-component-server-components]
```tsx showLineNumbers title="app/dashboard/layout.tsx"
import { IdentifySupportVisitor } from "@cossistant/next";
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
export default async function DashboardLayout({ children }) {
const session = await auth.api.getSession({
headers: await headers(),
});
return (
{session?.user && (
)}
{children}
);
}
```
Using the Hook (Client Components) [#using-the-hook-client-components]
```tsx showLineNumbers title="components/auth-handler.tsx"
"use client";
import { useVisitor } from "@cossistant/next";
import { useEffect } from "react";
export function AuthHandler({ user }) {
const { visitor, identify } = useVisitor();
useEffect(() => {
if (user && !visitor?.contact) {
identify({
externalId: user.id,
email: user.email,
name: user.name,
});
}
}, [user, visitor?.contact, identify]);
return null;
}
```
Once identified, all conversations and data are linked to the [contact](/docs/concepts/contacts), even across different devices.
Learn More [#learn-more]
* **[Contacts](/docs/concepts/contacts)**: Identified visitors with metadata and cross-device support
* **[IdentifySupportVisitor](/docs/support-component#identifying-visitors)**: Component for visitor identification
* **[useVisitor](/docs/support-component/hooks#usevisitor)**: Hook for programmatic visitor control
# Contacts
URL: /docs/concepts/contacts
What are Contacts? [#what-are-contacts]
**Contacts** are identified [visitors](/docs/concepts). When you identify an anonymous visitor with an `externalId` (your user ID) or `email`, they become a contact.
Contacts enable:
* **Cross-device support**: Same user on desktop and mobile shares conversation history
* **Rich metadata**: Attach context like plan type, MRR, company, or lifecycle stage
* **Dashboard visibility**: Support agents see user details alongside conversations
* **Persistent identity**: Conversations follow the user, not the device
Creating Contacts [#creating-contacts]
Identification Requirements [#identification-requirements]
A contact requires **at least one** of:
* `externalId`: Your internal user ID (recommended)
* `email`: User's email address
Both are accepted, but `externalId` is preferred for robust cross-system tracking.
Using the Component [#using-the-component]
```tsx showLineNumbers title="app/dashboard/layout.tsx"
import { IdentifySupportVisitor } from "@cossistant/next";
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
export default async function DashboardLayout({ children }) {
const session = await auth.api.getSession({
headers: await headers(),
});
return (
);
}
```
6\. Identify logged-in visitors (optional) [#6-identify-logged-in-visitors-optional]
```tsx title="app/(app)/layout.tsx"
import { IdentifySupportVisitor } from "@cossistant/next";
export default function AppLayout({ children }: { children: React.ReactNode }) {
const user = {
id: "user_123",
email: "jane@acme.com",
name: "Jane Doe",
};
return (
<>
{children}
>
);
}
```
7\. Display custom messages with `SupportConfig defaultMessages` [#7-display-custom-messages-with-supportconfig-defaultmessages]
```tsx title="app/page.tsx"
import { Support, SupportConfig } from "@cossistant/next";
import { type DefaultMessage, SenderType } from "@cossistant/types";
const user: { name: string | null } = {
name: "Jane Doe",
};
const defaultMessages: DefaultMessage[] = [
{
content: `Hi ${user.name ?? "there"}, anything I can help with?`,
senderType: SenderType.TEAM_MEMBER,
},
];
const quickOptions: string[] = ["How to identify a visitor?"];
export default function Page() {
return (
<>
>
);
}
```
You can now customize behavior in the [Support component guide](/docs/support-component).
# API Keys
URL: /docs/quickstart/api-keys
Get your public key [#get-your-public-key]
Open your dashboard at **Settings → Developers** and create or copy a **Public key**.
Configure environment variables [#configure-environment-variables]
Next.js
Vite
Other
```bash title=".env"
VITE_COSSISTANT_API_KEY=pk_live_xxxxxxxxxxxx
```
```bash title=".env.local"
NEXT_PUBLIC_COSSISTANT_API_KEY=pk_live_xxxxxxxxxxxx
```
```bash title=".env"
COSSISTANT_API_KEY=pk_live_xxxxxxxxxxxx
```
Auto-detection
The SDK automatically detects your framework and checks
`VITE_COSSISTANT_API_KEY` (Vite), `NEXT_PUBLIC_COSSISTANT_API_KEY`
(Next.js), or `COSSISTANT_API_KEY` (other). You can also pass the key
explicitly through `publicKey`.
Public vs private keys [#public-vs-private-keys]
| Type | Prefix | Purpose | Safe in browser |
| ----------- | ------------------------ | -------------------- | --------------- |
| **Public** | `pk_live_*`, `pk_test_*` | Widget integration | Yes |
| **Private** | `sk_live_*`, `sk_test_*` | Server-to-server API | No |
Only use **public** keys in frontend widget code.
Allowed domains [#allowed-domains]
Public keys work only on whitelisted domains.
* Production keys (`pk_live_*`) require explicit allowlisting.
* Test keys (`pk_test_*`) work on localhost.
Use full origins, for example:
* `https://example.com`
* `https://app.example.com`
* `https://staging.example.com`
Troubleshooting [#troubleshooting]
Configuration error in the widget [#configuration-error-in-the-widget]
Check:
* env variable name is correct for your framework
* key is still active
* app was restarted after env changes
Domain not allowed [#domain-not-allowed]
Check:
* domain is listed in **Settings → Developers → Allowed domains**
* protocol matches (`https://`)
# React
URL: /docs/quickstart/react
Quick start with AI prompt [#quick-start-with-ai-prompt]
Manually [#manually]
1\. Install the package [#1-install-the-package]
```bash
npm install @cossistant/react
```
2\. Add your public API key [#2-add-your-public-api-key]
Vite
Next.js
Other
```bash title=".env"
VITE_COSSISTANT_API_KEY=pk_test_xxxx
```
```bash title=".env.local"
NEXT_PUBLIC_COSSISTANT_API_KEY=pk_test_xxxx
```
For other frameworks (CRA, Remix, etc.), either set the env variable:
```bash title=".env"
COSSISTANT_API_KEY=pk_test_xxxx
```
Or pass the key directly via the `publicKey` prop:
```tsx
```
Auto-detection
The SDK automatically detects your framework and reads the right
environment variable (`VITE_COSSISTANT_API_KEY`, `NEXT_PUBLIC_COSSISTANT_API_KEY`,
or `COSSISTANT_API_KEY`). You can also pass the key explicitly through `publicKey`.
3\. Add `SupportProvider` [#3-add-supportprovider]
```tsx title="src/main.tsx"
import React from "react";
import ReactDOM from "react-dom/client";
import { SupportProvider } from "@cossistant/react";
import App from "./App";
import "./index.css";
ReactDOM.createRoot(document.getElementById("root")!).render(
,
);
```
Passing the key explicitly
If your framework does not support automatic env variable detection,
pass `publicKey` directly:
``.
4\. Import styles [#4-import-styles]
Tailwind v4
Plain CSS
```css title="src/index.css"
@import "tailwindcss";
@import "@cossistant/react/support.css";
```
```tsx title="src/main.tsx"
import React from "react";
import ReactDOM from "react-dom/client";
import { SupportProvider } from "@cossistant/react";
import "@cossistant/react/styles.css";
import App from "./App";
import "./index.css";
ReactDOM.createRoot(document.getElementById("root")!).render(
,
);
```
5\. Render the widget [#5-render-the-widget]
```tsx title="src/App.tsx"
import { Support } from "@cossistant/react";
export default function App() {
return (
You are ready to chat
);
}
```
6\. Identify logged-in visitors (optional) [#6-identify-logged-in-visitors-optional]
```tsx title="src/App.tsx"
import { IdentifySupportVisitor, Support } from "@cossistant/react";
export default function App() {
const user = {
id: "user_123",
email: "jane@acme.com",
name: "Jane Doe",
};
return (
<>
>
);
}
```
7\. Display custom messages with `SupportConfig defaultMessages` [#7-display-custom-messages-with-supportconfig-defaultmessages]
```tsx title="src/App.tsx"
import { Support, SupportConfig } from "@cossistant/react";
import { type DefaultMessage, SenderType } from "@cossistant/types";
const user: { name: string | null } = {
name: "Jane Doe",
};
const defaultMessages: DefaultMessage[] = [
{
content: `Hi ${user.name ?? "there"}, anything I can help with?`,
senderType: SenderType.TEAM_MEMBER,
},
];
const quickOptions: string[] = ["How to identify a visitor?"];
export default function App() {
return (
<>
>
);
}
```
You can now customize behavior in the [Support component guide](/docs/support-component).
# Basic usage
URL: /docs/support-component
Philosophy [#philosophy]
The `` component is built on **progressive disclosure**. Start with zero config, then customize as you grow.
How it Works [#how-it-works]
* **Zero Config**: Works out of the box with sensible defaults
* **Progressive Enhancement**: Start simple, customize as needed
* **Compound Components**: Radix-style API for full control when needed
* **Real-time Chat**: Built-in WebSocket support for live conversations
* **Headless Architecture**: Full control through primitive components when needed
Quick Start [#quick-start]
Drop `` anywhere in your app:
```tsx
import { Support } from "@cossistant/next";
export default function App() {
return (
{/* Your app */}
);
}
```
That's it. You get a fully functional support widget.
Customizing Messages & Quick Options [#customizing-messages--quick-options]
Use `` to customize messages and quick options per page:
```tsx title="app/pricing/page.tsx"
import { SupportConfig } from "@cossistant/next";
import { type DefaultMessage, SenderType } from "@cossistant/types";
const user: { name: string | null } = {
name: "Jane Doe",
};
const defaultMessages: DefaultMessage[] = [
{
content: `Hi ${user.name ?? "there"}, anything I can help with?`,
senderType: SenderType.TEAM_MEMBER,
},
];
const quickOptions: string[] = [
"How does billing work?",
"What's included in Pro?",
"Can I cancel anytime?",
];
export default function PricingPage() {
return (
<>
Choose your plan
>
);
}
```
Identifying Visitors [#identifying-visitors]
By default, visitors are **anonymous**. Use `` to connect them to your authenticated users:
```tsx title="app/dashboard/layout.tsx"
import { IdentifySupportVisitor } from "@cossistant/next";
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
export default async function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
const session = await auth.api.getSession({
headers: await headers(),
});
return (
{session?.user && (
)}
{children}
);
}
```
What happens when I identify a visitor?
The anonymous visitor becomes associated with a contact. All their
conversations, metadata, and future interactions are linked to this
contact—even across different devices. Learn more about{" "}
visitors and{" "}
contacts.
Props [#props]
Support Component Props [#support-component-props]
Usage Examples [#usage-examples]
Basic styling:
```tsx
```
With positioning:
```tsx
```
With custom page:
```tsx
import { Support, useSupportNavigation } from "@cossistant/next";
const HelpPage = () => {
const { goBack } = useSupportNavigation();
return (
Help Center
);
};
export default function App() {
return (
);
}
```
For advanced customization, see the [Customization](/docs/support-component/customization) guide.
Types [#types]
Shared support widget types now live on the
[Types](/docs/support-component/types) page. Use that reference for
`DefaultMessage`, `VisitorMetadata`, `SenderType`, `TriggerRenderProps`, and
the rest of the canonical support type docs.
# Customization
URL: /docs/support-component/customization
Overview [#overview]
The Support widget follows **progressive disclosure**. Start with zero config, add styling, then compose your own components when needed.
Level 1: Style Overrides [#level-1-style-overrides]
Override styles with `classNames`:
```tsx
```
Available classNames [#available-classnames]
* **`trigger`** - The floating chat button
* **`content`** - The main widget dialog
Level 2: Positioning [#level-2-positioning]
Control where the content appears relative to the trigger:
```tsx
```
Auto Collision Avoidance [#auto-collision-avoidance]
The widget automatically repositions itself to stay within the viewport. When there isn't enough space on the preferred side, it flips to the opposite side. It also shifts along the axis to prevent clipping at screen edges.
```tsx
// Collision avoidance is enabled by default
// Disable collision avoidance for static positioning
// Custom padding from viewport edges (default: 8px)
// Per-side collision padding
```
Level 3: Custom Trigger [#level-3-custom-trigger]
Replace the default trigger with ``:
```tsx
import { Support, type TriggerRenderProps } from "@cossistant/next";
{({ isOpen, unreadCount }: TriggerRenderProps) => (
<>
{isOpen ? "Close" : "Help"}
{unreadCount > 0 && (
{unreadCount}
)}
>
)}
```
Trigger Render Props [#trigger-render-props]
| Prop | Type | Description |
| ------------- | ------------ | ------------------------------------- |
| `isOpen` | `boolean` | Whether the widget is currently open |
| `unreadCount` | `number` | Number of unread messages |
| `isTyping` | `boolean` | Whether an agent is typing |
| `toggle` | `() => void` | Function to toggle widget open/closed |
Level 4: Custom Content [#level-4-custom-content]
Control the content positioning and styling with ``:
```tsx
{(props) => }
```
Level 5: Full Composition [#level-5-full-composition]
For complete control, use ``:
```tsx
import { Support } from "@cossistant/next";
export default function App() {
return (
);
}
```
Custom Trigger in a Topbar [#custom-trigger-in-a-topbar]
A common use case is placing the trigger in your navigation bar:
```tsx
import { Support, type TriggerRenderProps } from "@cossistant/next";
function Topbar() {
return (
);
}
```
Custom Pages [#custom-pages]
Add custom pages to the router:
```tsx
import { Support, useSupportNavigation } from "@cossistant/next";
const FAQPage = () => {
const { goBack } = useSupportNavigation();
return (
Frequently Asked Questions
);
};
```
Or with the router directly:
```tsx
```
Type Reference [#type-reference]
TriggerRenderProps [#triggerrenderprops]
```tsx
type TriggerRenderProps = {
isOpen: boolean;
isTyping: boolean;
unreadCount: number;
toggle: () => void;
};
```
ContentProps [#contentprops]
```tsx
type CollisionPadding =
| number
| { top?: number; right?: number; bottom?: number; left?: number };
type ContentProps = {
className?: string;
side?: "top" | "bottom" | "left" | "right";
align?: "start" | "center" | "end";
sideOffset?: number;
avoidCollisions?: boolean; // default: true
collisionPadding?: CollisionPadding; // default: 8
children?: React.ReactNode;
};
```
SupportProps [#supportprops]
```tsx
type SupportProps = {
className?: string;
side?: "top" | "bottom" | "left" | "right";
align?: "start" | "center" | "end";
sideOffset?: number;
avoidCollisions?: boolean; // default: true
collisionPadding?: CollisionPadding; // default: 8
classNames?: {
trigger?: string;
content?: string;
};
theme?: "light" | "dark";
defaultOpen?: boolean;
locale?: string;
content?: SupportTextContentOverrides;
quickOptions?: string[];
defaultMessages?: DefaultMessage[];
customPages?: CustomPage[];
children?: React.ReactNode;
};
```
Full Example [#full-example]
Combining multiple customizations:
```tsx
import { Support, type TriggerRenderProps } from "@cossistant/next";
import { MessageCircle, X } from "lucide-react";
const TriggerContent = ({ isOpen, unreadCount }: TriggerRenderProps) => (
<>
{isOpen ? : }
{unreadCount > 0 && (
{unreadCount}
)}
>
);
export default function App() {
return (
{(props) => }
);
}
```
# Events
URL: /docs/support-component/events
Overview [#overview]
The Support widget emits events throughout its lifecycle, allowing you to:
* Track user interactions for analytics
* Log events for debugging
* Trigger custom behavior based on widget activity
* Integrate with third-party services
Event Callback Props [#event-callback-props]
The simplest way to listen to events is via callback props on the `` component:
```tsx showLineNumbers title="app/layout.tsx"
"use client";
import { Support } from "@cossistant/next";
export function SupportWidget() {
return (
{
analytics.track("support_conversation_started", { conversationId });
}}
onConversationEnd={({ conversationId }) => {
analytics.track("support_conversation_ended", { conversationId });
}}
onMessageSent={({ conversationId, message }) => {
analytics.track("support_message_sent", {
conversationId,
messageId: message.id,
});
}}
onMessageReceived={({ conversationId, message }) => {
analytics.track("support_message_received", {
conversationId,
messageId: message.id,
});
}}
onError={({ error, context }) => {
logger.error("Support widget error", { error, context });
}}
/>
);
}
```
Available Events [#available-events]
onConversationStart [#onconversationstart]
Fired when a new conversation is created.
onConversationEnd [#onconversationend]
Fired when a conversation is closed or resolved.
onMessageSent [#onmessagesent]
Fired when the visitor sends a message.
onMessageReceived [#onmessagereceived]
Fired when a message is received from an agent (human or AI).
onError [#onerror]
Fired when an error occurs within the widget.
Programmatic Event Handling [#programmatic-event-handling]
For more control, use the `useSupportEvents` hook to subscribe to events from any component within the widget:
```tsx showLineNumbers title="components/analytics-tracker.tsx"
"use client";
import { useSupportEvents } from "@cossistant/next/support";
import { useEffect } from "react";
export function AnalyticsTracker() {
const events = useSupportEvents();
useEffect(() => {
if (!events) return;
// Subscribe to multiple event types
const unsubscribes = [
events.subscribe("conversationStart", (event) => {
analytics.track("conversation_started", event);
}),
events.subscribe("messageSent", (event) => {
analytics.track("message_sent", event);
}),
events.subscribe("messageReceived", (event) => {
analytics.track("message_received", event);
}),
events.subscribe("error", (event) => {
errorTracker.captureException(event.error, {
extra: { context: event.context },
});
}),
];
// Cleanup all subscriptions
return () => {
unsubscribes.forEach((unsubscribe) => unsubscribe());
};
}, [events]);
return null;
}
```
Where to place the tracker
The `AnalyticsTracker` component should be rendered inside the Support
widget tree or within a component wrapped by `SupportProvider`. The events
context is scoped to the widget instance.
Emitting Custom Events [#emitting-custom-events]
From within custom pages or components, use `useSupportEventEmitter` to emit events:
```tsx showLineNumbers title="pages/custom-conversation.tsx"
"use client";
import { useSupportEventEmitter } from "@cossistant/next/support";
export function CustomConversationPage({ conversationId }: { conversationId: string }) {
const emitter = useSupportEventEmitter();
const handleSendMessage = async (message: string) => {
try {
const sentMessage = await sendMessage(conversationId, message);
// Emit the event for other listeners
emitter.emitMessageSent(conversationId, sentMessage);
} catch (error) {
emitter.emitError(error as Error, "message_send_failed");
}
};
return (
// ...your component
);
}
```
Integration Examples [#integration-examples]
Google Analytics 4 [#google-analytics-4]
```tsx showLineNumbers title="components/ga-tracker.tsx"
"use client";
import { Support } from "@cossistant/next";
export function SupportWithGA() {
return (
{
gtag("event", "support_conversation_start", {
conversation_id: conversationId,
});
}}
onMessageSent={({ conversationId, message }) => {
gtag("event", "support_message_sent", {
conversation_id: conversationId,
message_id: message.id,
});
}}
/>
);
}
```
Sentry Error Tracking [#sentry-error-tracking]
```tsx showLineNumbers title="components/sentry-support.tsx"
"use client";
import { Support } from "@cossistant/next";
import * as Sentry from "@sentry/react";
export function SupportWithSentry() {
return (
{
Sentry.captureException(error, {
tags: {
component: "support_widget",
},
extra: {
context,
},
});
}}
/>
);
}
```
Segment [#segment]
```tsx showLineNumbers title="components/segment-support.tsx"
"use client";
import { Support } from "@cossistant/next";
import { analytics } from "./analytics";
export function SupportWithSegment() {
return (
{
analytics.track("Support Conversation Started", {
conversationId,
});
}}
onConversationEnd={({ conversationId }) => {
analytics.track("Support Conversation Ended", {
conversationId,
});
}}
onMessageSent={({ conversationId, message }) => {
analytics.track("Support Message Sent", {
conversationId,
messageId: message.id,
hasAttachments: message.parts.some(
(part) => part.type === "image" || part.type === "file"
),
});
}}
/>
);
}
```
PostHog [#posthog]
```tsx showLineNumbers title="components/posthog-support.tsx"
"use client";
import { Support } from "@cossistant/next";
import { usePostHog } from "posthog-js/react";
export function SupportWithPostHog() {
const posthog = usePostHog();
return (
{
posthog.capture("support_conversation_started", {
conversationId,
});
}}
onMessageSent={({ conversationId }) => {
posthog.capture("support_message_sent", {
conversationId,
});
}}
/>
);
}
```
Event Types [#event-types]
All event types are exported for TypeScript usage:
```tsx
import type {
SupportEvent,
SupportEventType,
ConversationStartEvent,
ConversationEndEvent,
MessageSentEvent,
MessageReceivedEvent,
ErrorEvent,
SupportEventCallbacks,
} from "@cossistant/next/support";
```
# Hooks
URL: /docs/support-component/hooks
Overview [#overview]
Cossistant provides React hooks for programmatic control over every aspect of the support widget. These hooks follow a layered architecture:
* **Core hooks** (`useSupport`, `useVisitor`) - Primary integration points
* **Widget state hooks** (`useSupportConfig`, `useSupportNavigation`) - Control widget behavior
* **Page hooks** (`useHomePage`, `useConversationPage`) - Build custom pages
* **Utility hooks** (`useFileUpload`, `useMessageComposer`) - Compose functionality
useSupport [#usesupport]
Access support widget state and controls from any client component.
Basic Example [#basic-example]
```tsx showLineNumbers title="components/custom-support-button.tsx"
"use client";
import { useSupport } from "@cossistant/next";
export function CustomSupportButton() {
const { isOpen, toggle, unreadCount } = useSupport();
return (
);
}
```
Return Values [#return-values]
useVisitor [#usevisitor]
Programmatically identify visitors and manage contact metadata.
Important: Metadata storage
Metadata is stored on **contacts**, not visitors. You must call `identify()`
before `setVisitorMetadata()` will work. Learn more about{" "}
visitors and{" "}
contacts.
Example: Identify on Auth [#example-identify-on-auth]
```tsx showLineNumbers title="components/auth-handler.tsx"
"use client";
import { useVisitor } from "@cossistant/next";
import { useEffect } from "react";
export function AuthHandler({ user }) {
const { visitor, identify } = useVisitor();
useEffect(() => {
// Only identify if we have a user and visitor isn't already a contact
if (user && !visitor?.contact) {
identify({
externalId: user.id,
email: user.email,
name: user.name,
image: user.avatar,
});
}
}, [user, visitor?.contact, identify]);
return null;
}
```
Example: Update Metadata on Action [#example-update-metadata-on-action]
```tsx showLineNumbers title="components/upgrade-button.tsx"
"use client";
import { useVisitor } from "@cossistant/next";
export function UpgradeButton() {
const { setVisitorMetadata } = useVisitor();
const handleUpgrade = async () => {
// Upgrade user's plan
await upgradeToPro();
// Update contact metadata so support agents see the change
await setVisitorMetadata({
plan: "pro",
upgradedAt: new Date().toISOString(),
mrr: 99,
});
};
return ;
}
```
Return Values [#return-values-1]
identify() Parameters [#identify-parameters]
Prefer declarative code?
Use the{" "}
IdentifySupportVisitor
{" "}
component for a simpler, declarative approach to visitor identification in
Server Components.
useSupportConfig [#usesupportconfig]
Access and control widget visibility and size configuration.
Basic Example [#basic-example-1]
```tsx showLineNumbers title="components/custom-toggle.tsx"
"use client";
import { useSupportConfig } from "@cossistant/next/support";
export function CustomToggle() {
const { isOpen, open, close, toggle, size } = useSupportConfig();
return (
Size: {size}
);
}
```
Return Values [#return-values-2]
Controlled mode support
When using controlled mode (`open` and `onOpenChange` props on Support), these functions
will call `onOpenChange` instead of modifying internal state.
useSupportNavigation [#usesupportnavigation]
Access navigation state and routing methods for the widget.
Basic Example [#basic-example-2]
```tsx showLineNumbers title="components/navigation-buttons.tsx"
"use client";
import { useSupportNavigation } from "@cossistant/next/support";
export function NavigationButtons() {
const { page, navigate, goBack, canGoBack } = useSupportNavigation();
return (
{canGoBack && }
Current page: {page}
);
}
```
Return Values [#return-values-3]
useSupportHandle [#usesupporthandle]
Access the imperative handle from within the widget tree. Alternative to using refs on the Support component. The hook returns `null` outside the widget tree, and the table below describes the handle when it is available.
Basic Example [#basic-example-3]
```tsx showLineNumbers title="components/help-button.tsx"
"use client";
import { useSupportHandle } from "@cossistant/next/support";
export function HelpButton() {
const support = useSupportHandle();
const handleNeedHelp = () => {
// Open support and start a new conversation
support?.startConversation("I need help with my order");
};
return (
);
}
```
Return Values [#return-values-4]
useHomePage [#usehomepage]
Logic hook for building custom home pages. Provides all state and actions needed for the home page.
Basic Example [#basic-example-4]
```tsx showLineNumbers title="pages/custom-home.tsx"
"use client";
import { useHomePage } from "@cossistant/next";
export function CustomHomePage() {
const home = useHomePage({
onStartConversation: () => console.log("Conversation started"),
onOpenConversation: (id) => console.log("Opened:", id),
});
return (
);
}
```
Return Values [#return-values-8]
useSupportText [#usesupporttext]
Access the localization system for the support widget.
Basic Example [#basic-example-8]
```tsx showLineNumbers title="components/localized-button.tsx"
"use client";
import { useSupportText } from "@cossistant/next/support";
export function LocalizedButton() {
const format = useSupportText();
return (
);
}
```
Returned Formatter [#returned-formatter]
`useSupportText()` returns a formatter function. The table below documents that
function reference.
useSupportEvents [#usesupportevents]
Access the events context for subscribing to widget lifecycle events. The hook returns `null` when used outside the widget's event provider, and the table below documents the event context when present.
Basic Example [#basic-example-9]
```tsx showLineNumbers title="components/analytics-tracker.tsx"
"use client";
import { useSupportEvents } from "@cossistant/next/support";
import { useEffect } from "react";
export function AnalyticsTracker() {
const events = useSupportEvents();
useEffect(() => {
if (!events) return;
const unsubscribe = events.subscribe("messageSent", (event) => {
// Track in your analytics
analytics.track("support_message_sent", {
conversationId: event.conversationId,
});
});
return unsubscribe;
}, [events]);
return null;
}
```
Return Values [#return-values-9]
useSupportEventEmitter [#usesupporteventemitter]
Convenience hook for emitting events from within the widget.
Return Values [#return-values-10]
Types [#types]
Shared support hook and data-model types now live on the
[Types](/docs/support-component/types) page. Use it for `PublicVisitor`,
`PublicWebsiteResponse`, `CossistantClient`, `TimelineItem`, `Conversation`,
and the rest of the canonical support type reference.
# Primitives
URL: /docs/support-component/primitives
Philosophy [#philosophy]
Primitives are **headless UI components** that give you complete control over styling and behavior. Inspired by **shadcn/ui**, they follow these principles:
* **Unstyled by default** - Bring your own Tailwind classes
* **Fully composable** - Build complex UIs from simple pieces
* **Developer-owned** - Copy, modify, and extend as needed
* **Accessible** - Built-in ARIA patterns and keyboard navigation
Use primitives when you need **complete control** over the support experience.
Import [#import]
Access primitives via the `Primitives` namespace:
```tsx
import { Primitives } from "@cossistant/next";
// Use individual primitives
.........
```
Or import the namespace with an alias:
```tsx
import { Primitives as P } from "@cossistant/next";
...
```
Quick Example [#quick-example]
```tsx
import { Primitives, useSupportConfig } from "@cossistant/next";
function CustomWidget() {
const { isOpen, toggle } = useSupportConfig();
return (
<>
{({ toggle, unreadCount }) => (
)}
{({ isOpen, close }) => (
isOpen && (
Custom support content
)
)}
>
);
}
```
Primitives Reference [#primitives-reference]
Core Components [#core-components]
**``**
Trigger button with widget state. Can be placed anywhere in the DOM.
```tsx
{({ isOpen, unreadCount, isTyping, toggle }) => (
)}
```
**``**
Dialog container with open/close state and escape key handling.
```tsx
{({ isOpen, close }) => (
isOpen && (
Window content
)
)}
```
Timeline & Messages [#timeline--messages]
**``**
Message timeline container with automatic scrolling and loading states.
```tsx
{messages.map(msg => (
))}
```
**``**
Individual message or event in the timeline.
```tsx
{message.text}
{message.createdAt}
```
**``**
Group consecutive messages by the same sender.
```tsx
{user.name}
{messages.map(msg => (
))}
```
Input & Interaction [#input--interaction]
**``**
Rich text input with file upload support.
```tsx
```
**``**
File upload component with drag-and-drop.
```tsx
```
**`