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.
The TSX examples use Tailwind utilities for brevity. Tailwind is not required:
replace those class names with your own CSS when you use the precompiled
@cossistant/react/styles.css entrypoint.
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);
}When you replace a slot, you inherit its accessibility responsibilities. Keep the forwarded ref and button props, preserve the trigger's accessible name and focus styles, make the target at least 44 by 44 CSS pixels, and announce unread counts with meaningful text when they matter to the user.
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
Was this page helpful?
Open a prefilled documentation issue so the team can act on your feedback.