Widget v0.2.0NEW: Script embeds and AI clarification
Get Started
<Support />
Advanced
Change One Thing
Keep the default widget and swap one small piece at a time.
Support is built for small edits. Start with styling, then swap one component, and keep the rest of the widget working as-is.
Use this when
- you want the widget live fast
- you need the bubble or first screen to feel more like your product
- you want safe overrides without rebuilding the router or conversation UI
Smallest working change
import { Support } from "@cossistant/react";
<Support
classNames={{
trigger: "bg-black text-white",
content: "border-neutral-200 shadow-2xl",
}}
slotProps={{
content: {
className: "border border-neutral-200 shadow-2xl",
},
}}
/>;Use classNames for the built-in trigger and content. Use slotProps when you want to pass extra presentational props to built-in parts.
The default widget also exposes stable styling hooks:
data-slotdata-statedata-page
[data-slot="trigger"][data-state="open"] {
transform: scale(1.02);
}
[data-slot="content"] {
backdrop-filter: blur(12px);
}Bubble example 1
Use slots.trigger when the launcher is the only thing that should change.
import { Support, type SupportTriggerSlotProps } from "@cossistant/react";
import * as React from "react";
function mergeClassNames(...classes: Array<string | undefined>) {
return classes.filter(Boolean).join(" ");
}
const ClassicBubble = React.forwardRef<HTMLButtonElement, SupportTriggerSlotProps>(
function ClassicBubbleTrigger(
{ className, isOpen, isTyping: _isTyping, toggle, unreadCount, ...props },
ref
) {
return (
<button
{...props}
className={mergeClassNames(
"relative flex size-14 items-center justify-center border border-black bg-black font-medium text-white text-sm shadow-xl transition-transform hover:scale-[1.02]",
className
)}
onClick={toggle}
ref={ref}
type="button"
>
{isOpen ? "Close" : "Chat"}
{unreadCount > 0 ? (
<span className="-right-1 -top-1 absolute flex size-5 items-center justify-center border border-white bg-orange-500 text-[11px]">
{unreadCount}
</span>
) : null}
</button>
);
}
);
<Support slots={{ trigger: ClassicBubble }} />;Bubble example 2
The same slot can feel more like an in-product button instead of a floating chat bubble.
import { Support, type SupportTriggerSlotProps } from "@cossistant/react";
import * as React from "react";
function mergeClassNames(...classes: Array<string | undefined>) {
return classes.filter(Boolean).join(" ");
}
const PillBubble = React.forwardRef<HTMLButtonElement, SupportTriggerSlotProps>(
function PillBubble(
{
className,
isOpen: _isOpen,
isTyping,
unreadCount: _unreadCount,
toggle,
...props
},
ref
) {
return (
<button
{...props}
className={mergeClassNames(
"flex h-12 items-center gap-2 border border-black bg-white px-4 font-medium text-black text-sm shadow-xl transition-transform hover:scale-[1.01]",
className
)}
onClick={toggle}
ref={ref}
type="button"
>
<span className="size-2 bg-orange-500" />
<span>{isTyping ? "Support is typing..." : "Open support"}</span>
</button>
);
}
);
<Support slots={{ trigger: PillBubble }} />;Replace the first screen
Keep the default conversation flow and change only the home page.
import { Support, type SupportHomePageSlotProps } from "@cossistant/react";
function CustomHomePage({
className,
openConversationHistory,
quickOptions,
startConversation,
website,
}: SupportHomePageSlotProps) {
return (
<div className={className}>
<h2>{website?.name}</h2>
<p>Rewrite the first screen while keeping the default conversation flow.</p>
{quickOptions.map((option) => (
<button key={option} onClick={() => startConversation(option)} type="button">
{option}
</button>
))}
<button onClick={openConversationHistory} type="button">
View past conversations
</button>
</div>
);
}
<Support
slotProps={{
content: {
className: "border shadow-2xl",
},
}}
slots={{ homePage: CustomHomePage }}
/>;Cossistant
Real support, instantly.
Ask about onboarding, setup, billing, or migration without leaving the product.
When to stop here
- your widget is in production
- branding changes fit inside
classNames,slotProps, or one slot override - you still want the default router, conversation page, and composer
Next step
- Match Your Brand for colors, radius, and dark mode
- Pages & Layouts when you need custom pages or an inline embed