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-slot
  • data-state
  • data-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