Overview
The Support widget follows progressive disclosure. Start with zero config, add styling, then compose your own components when needed.
Level 1: Style Overrides
Override styles with classNames:
<Support
classNames={{
trigger: "bg-purple-600 hover:bg-purple-700",
content: "border-purple-200 shadow-2xl",
}}
/>Available classNames
trigger- The floating chat buttoncontent- The main widget dialog
Level 2: Positioning
Control where the content appears relative to the trigger:
<Support
side="bottom" // "top" | "bottom" | "left" | "right"
align="end" // "start" | "center" | "end"
sideOffset={16} // Distance in pixels
/>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.
// Collision avoidance is enabled by default
<Support side="top" align="end" />
// Disable collision avoidance for static positioning
<Support avoidCollisions={false} />
// Custom padding from viewport edges (default: 8px)
<Support collisionPadding={16} />
// Per-side collision padding
<Support collisionPadding={{ top: 8, bottom: 32, left: 8, right: 8 }} />Level 3: Custom Trigger
Replace the default trigger with <Support.Trigger>:
import { Support, type TriggerRenderProps } from "@cossistant/react";
<Support side="bottom" align="end">
<Support.Trigger className="flex items-center gap-2 px-4 py-2 bg-blue-600 rounded-lg">
{({ isOpen, unreadCount }: TriggerRenderProps) => (
<>
<span>{isOpen ? "Close" : "Help"}</span>
{unreadCount > 0 && (
<span className="bg-red-500 text-white text-xs px-1.5 rounded-full">
{unreadCount}
</span>
)}
</>
)}
</Support.Trigger>
</Support>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
Control the content positioning and styling with <Support.Content>:
<Support>
<Support.Trigger className="...">
{(props) => <MyTriggerContent {...props} />}
</Support.Trigger>
<Support.Content
side="bottom"
align="end"
sideOffset={8}
className="border-2 border-purple-500"
>
<Support.Router />
</Support.Content>
</Support>Level 5: Full Composition
For complete control, use <Support.Root>:
import { Support } from "@cossistant/react";
export default function App() {
return (
<Support.Root defaultOpen={false} theme="dark">
<Support.Trigger asChild>
<button className="px-4 py-2 bg-indigo-600 rounded" type="button">
Help
</button>
</Support.Trigger>
<Support.Content side="bottom" align="end" className="custom-content">
<Support.Router />
</Support.Content>
</Support.Root>
);
}Custom Trigger in a Topbar
A common use case is placing the trigger in your navigation bar:
import { Support, type TriggerRenderProps } from "@cossistant/react";
function Topbar() {
return (
<header className="flex items-center justify-between px-4 h-16">
<Logo />
<nav className="flex items-center gap-4">
<Link href="/settings">Settings</Link>
<Support side="bottom" align="end" sideOffset={8}>
<Support.Trigger className="flex items-center gap-2 px-3 py-2 rounded-md hover:bg-gray-100">
{({ isOpen, unreadCount }: TriggerRenderProps) => (
<>
<span>{isOpen ? "Close" : "Help"}</span>
{unreadCount > 0 && (
<span className="bg-red-500 text-white text-xs px-1.5 rounded-full">
{unreadCount}
</span>
)}
</>
)}
</Support.Trigger>
</Support>
</nav>
</header>
);
}Custom Pages
Add custom pages to the router:
import { Support, useSupportNavigation } from "@cossistant/react";
const FAQPage = () => {
const { goBack } = useSupportNavigation();
return (
<div className="p-4">
<button onClick={goBack} type="button">← Back</button>
<h1>Frequently Asked Questions</h1>
</div>
);
};
<Support>
<Support.Page name="FAQ" component={FAQPage} />
</Support>Or with the router directly:
<Support>
<Support.Content>
<Support.Router>
<Support.Page name="FAQ" component={FAQPage} />
<Support.Page name="SETTINGS" component={SettingsPage} />
</Support.Router>
</Support.Content>
</Support>Type Reference
TriggerRenderProps
type TriggerRenderProps = {
isOpen: boolean;
isTyping: boolean;
unreadCount: number;
toggle: () => void;
};ContentProps
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
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
Combining multiple customizations:
import { Support, type TriggerRenderProps } from "@cossistant/react";
import { AnimatePresence, motion } from "motion/react";
import { MessageCircle, X } from "lucide-react";
const AnimatedTriggerContent = ({ isOpen, unreadCount }: TriggerRenderProps) => (
<>
<AnimatePresence mode="wait">
{isOpen ? (
<motion.div
key="close"
initial={{ scale: 0.8, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.8, opacity: 0 }}
>
<X className="size-5" />
</motion.div>
) : (
<motion.div
key="open"
initial={{ scale: 0.8, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.8, opacity: 0 }}
>
<MessageCircle className="size-6" />
</motion.div>
)}
</AnimatePresence>
{unreadCount > 0 && (
<span className="absolute -top-1 -right-1 flex size-5 items-center justify-center rounded-full bg-red-500 text-white text-xs">
{unreadCount}
</span>
)}
</>
);
export default function App() {
return (
<Support theme="dark" side="top" align="end">
<Support.Trigger className="relative flex size-14 items-center justify-center rounded-full bg-indigo-600 text-white hover:bg-indigo-700">
{(props) => <AnimatedTriggerContent {...props} />}
</Support.Trigger>
<Support.Content className="border-indigo-200" />
</Support>
);
}On this page
OverviewLevel 1: Style OverridesAvailable classNamesLevel 2: PositioningAuto Collision AvoidanceLevel 3: Custom TriggerTrigger Render PropsLevel 4: Custom ContentLevel 5: Full CompositionCustom Trigger in a TopbarCustom PagesType ReferenceTriggerRenderPropsContentPropsSupportPropsFull Example