Skip to content

Screens

Support Widget

A support bot embedded on a website — launcher bubble, quick replies, order lookup, handoff to a human and a rating at the end.

2 variantsMIT

Preview

Full screen

New season

Gear for the long way round.

Free returns for 60 days, repairs for life.

  • Trail Runner 2$139
  • Summit Shell Jacket$249
  • Ridge 28L Pack$119

Installation

Copies the screen into components/ and installs every component it uses, with their dependencies and theme tokens.

pnpm dlx shadcn@latest add https://tuffui.com/r/support-widget.json

Usage

Pass your own data and wire the callbacks to your agent runtime.

import { SupportWidget } from "@/components/support-widget";

export function SiteWithSupport({ chat, children }: { chat: SupportChat; children: React.ReactNode }) {
  return (
    <SupportWidget
      data={{
        bot: { name: "Ridge" },
        human: chat.assignee ? { name: chat.assignee.name, role: "Support team" } : undefined,
        visitor: { name: chat.visitor.name },
        // content is any React node — order cards, questions, forms.
        messages: chat.messages.map((m) => ({
          id: m.id,
          role: m.from === "visitor" ? "user" : "assistant",
          author: m.from === "agent" ? chat.assignee?.name : undefined,
          at: m.createdAt,
          content: m.text,
        })),
        quickReplies: chat.messages.length < 2 ? chat.quickReplies : undefined,
        mode: chat.assignee ? "human" : chat.queued ? "waiting" : chat.botTyping ? "typing" : "bot",
        wait: chat.queueEstimate,
        askRating: chat.closed,
        unread: chat.unread,
      }}
      onSend={(text) => chat.send(text)}
      onRate={(rating) => chat.rate(rating)}
      onClose={() => chat.end()}
    >
      {children}
    </SupportWidget>
  );
}

Props

PropTypeDefaultDescription
data*SupportWidgetData—Bot, the person who takes over, the visitor, the turns, quick replies and the current mode.
childrenReactNode—The host page the widget floats over. It stays usable while the panel is open.
open / defaultOpen / onOpenChangeboolean / boolean / (open: boolean) => void— / false / —Whether the panel is open. The launcher toggles it, Esc and the minimise button close it.
onSend(text: string) => void—A message from the composer or a quick reply.
onRate(feedback) => void—The 1–5 rating, reasons and note from the end-of-chat rating.
onClose() => void—The end-chat button. Minimising only hides the panel.
classNamestring—Extra classes for the root.
type SupportPerson = { name: string; avatar?: string; role?: string };

type SupportWidgetData = {
  bot: SupportPerson;
  human?: SupportPerson;               // set message.author to human.name for their turns
  visitor: { name: string };
  messages: ThreadMessage[];           // from chat-thread; content is a ReactNode
  quickReplies?: PromptSuggestion[];   // chips under the last bot message
  mode?: "bot" | "typing" | "waiting" | "human";
  wait?: string;                       // queue estimate, e.g. "~2 min"
  askRating?: boolean;
  unread?: number;                     // badge on the launcher while closed
};

// The widget fills its parent and lays out by its own width: from 28rem the panel
// floats bottom-right over the page, below that it fills the screen.