> ## Documentation Index
> Fetch the complete documentation index at: https://docs.colossal.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Widget components

> The chat widget's built-in components, and custom React renderers for automation outputs.

The iframe embed is zero-code but fixed-function. With the React SDK (`@colossal-sh/chat-react`) you host the widget inside your own app and extend it with **custom component renderers**: when an automation returns an output with a `type` you've registered, the widget renders your component instead of plain text.

The flow has two halves:

1. **In your app** — define a component type and register a renderer for it.
2. **In the automation builder** — ask for automations to return their result in the output format that type needs.

## 1. Register a component renderer

```bash theme={null}
npm install @colossal-sh/chat-react @colossal-sh/chat-client
```

```tsx theme={null}
import { createChatClient } from "@colossal-sh/chat-client";
import { ChatProvider, ChatWidget } from "@colossal-sh/chat-react";
import type { ComponentOutputProps } from "@colossal-sh/chat-react";
import "@colossal-sh/chat-react/styles.css";

const chatClient = createChatClient({
  agentUid: "<agent-uid>",
  adapter: { baseUrl: "https://<your-workspace-host>" },
});

function WarrantyCard({ payload }: ComponentOutputProps) {
  return (
    <div className="warranty-card">
      <strong>{String(payload.productTitle)}</strong>
      <span>{payload.active ? "Under warranty" : "Expired"}</span>
      <span>until {String(payload.expiresAt)}</span>
    </div>
  );
}

export function SupportChat() {
  return (
    <ChatProvider
      client={chatClient}
      componentRenderers={{ warranty_check: WarrantyCard }}
    >
      <ChatWidget />
    </ChatProvider>
  );
}
```

Create the client at module level and pass it via the `client` prop. With the `config` prop the provider creates the client after the first render, so components that read the chat context on mount (including `ChatWidget`) throw "must be inside `<ChatProvider>`" unless you gate them with `useHasChatClient()`.

`componentRenderers` maps a `type` string to a React component. The renderer receives the whole output object as `payload`, plus the raw tool `part` and `message` if you need them.

## 2. Ask the builder for that output format

An output is matched to a renderer when the step result the agent responds with is a JSON object carrying your `type`. When building the automation, tell the builder the exact shape your component expects:

> When a customer asks about warranty, look up the order and finish with an output of the form
> `{ "type": "warranty_check", "productTitle": ..., "active": true/false, "expiresAt": ... }`.

The builder wires the automation's final step to produce that object. At runtime the widget sees `type: "warranty_check"`, finds your renderer, and renders the card in the conversation.

If no renderer matches a `type`, the widget falls back to its default output rendering — so shipping the automation before the frontend (or vice versa) degrades gracefully.

## Built-in component types

These types are rendered out of the box, and you can override any of them by registering your own renderer under the same key. All amounts are in minor units (cents).

<AccordionGroup>
  <Accordion title="Product List">
    Type: `product_list` — a horizontally scrollable list of product cards. Each card shows an **Add** button, which turns into a quantity stepper once the item is in the cart:

    <div className="widget-preview">
      <div className="scw-product-list">
        <div className="scw-component-header">Search results</div>

        <div className="scw-products">
          <div className="scw-product-card">
            <img className="scw-product-img" src="https://pub-74ac413420c240d4b6830500ed2351c0.r2.dev/uploads/5fd74f04-f442-437c-9ec9-d4832f06b970product-image.png" alt="Minimal Canvas Tote Bag" />

            <div className="scw-product-name">Minimal Canvas Tote Bag</div>
            <div className="scw-product-price">\$24.00</div>

            <div className="scw-product-qty">
              <span className="scw-qty-button">−</span>
              <span className="scw-qty-count">1</span>
              <span className="scw-qty-button">+</span>
            </div>
          </div>

          <div className="scw-product-card">
            <img className="scw-product-img" src="https://pub-74ac413420c240d4b6830500ed2351c0.r2.dev/uploads/018c6591-5dfb-4153-8c06-28daa329d8cbproduct-image.png" alt="Ceramic Coffee Mug" />

            <div className="scw-product-name">Ceramic Coffee Mug</div>
            <div className="scw-product-price">\$18.00</div>
            <div className="scw-product-add">Add</div>
          </div>

          <div className="scw-product-card">
            <img className="scw-product-img" src="https://pub-74ac413420c240d4b6830500ed2351c0.r2.dev/uploads/a3621e28-24ac-4564-84f8-3b7c03d1ce33product-image.png" alt="Soy Wax Candle" />

            <div className="scw-product-name">Soy Wax Candle</div>
            <div className="scw-product-price">\$22.00</div>
            <div className="scw-product-add">Add</div>
          </div>
        </div>
      </div>
    </div>

    **Payload**

    <ResponseField name="products" type="array" required>
      The products to show. Renders nothing when empty. Each item:

      <Expandable title="product">
        <ParamField path="uid" type="string">
          Product identifier.
        </ParamField>

        <ParamField path="name" type="string">
          Product title.
        </ParamField>

        <ParamField path="price_amount" type="number">
          Price in minor units, formatted with `currency`.
        </ParamField>

        <ParamField path="currency" type="string">
          Currency code, e.g. `USD`.
        </ParamField>

        <ParamField path="image_url" type="string">
          Product image URL.
        </ParamField>

        <ParamField path="url" type="string">
          Link target for the card.
        </ParamField>
      </Expandable>
    </ResponseField>
  </Accordion>

  <Accordion title="Cart Updated">
    Types: `cart_add`, `cart_remove` — a confirmation chip reading "Cart updated" for `cart_add`, "Item removed" for `cart_remove`.

    **Payload**

    No fields beyond `type`; the label is chosen from the type itself.
  </Accordion>

  <Accordion title="Cart View">
    Type: `cart_view` — a summary card of the customer's current cart, or an empty-cart state. Quantity steppers adjust items in place; on Shopify embeds the cart bridge keeps this in sync with the real storefront cart:

    <div className="widget-preview">
      <div className="scw-product-list">
        <div className="scw-component-header">Cart</div>

        <div className="scw-products">
          <div className="scw-product-card">
            <img className="scw-product-img" src="https://pub-74ac413420c240d4b6830500ed2351c0.r2.dev/uploads/5fd74f04-f442-437c-9ec9-d4832f06b970product-image.png" alt="Minimal Canvas Tote Bag" />

            <div className="scw-product-name">Minimal Canvas Tote Bag</div>
            <div className="scw-product-price">\$24.00</div>

            <div className="scw-product-qty">
              <span className="scw-qty-button">−</span>
              <span className="scw-qty-count">2</span>
              <span className="scw-qty-button">+</span>
            </div>
          </div>

          <div className="scw-product-card">
            <img className="scw-product-img" src="https://pub-74ac413420c240d4b6830500ed2351c0.r2.dev/uploads/018c6591-5dfb-4153-8c06-28daa329d8cbproduct-image.png" alt="Ceramic Coffee Mug" />

            <div className="scw-product-name">Ceramic Coffee Mug</div>
            <div className="scw-product-price">\$18.00</div>

            <div className="scw-product-qty">
              <span className="scw-qty-button">−</span>
              <span className="scw-qty-count">1</span>
              <span className="scw-qty-button">+</span>
            </div>
          </div>
        </div>
      </div>
    </div>

    **Payload**

    No fields beyond `type`; the card reads the live cart from the widget's cart state rather than the payload.
  </Accordion>

  <Accordion title="Order Details">
    Type: `order_details` — an order summary card with line items, total, addresses, and status, plus a tracking link when available:

    <div className="widget-preview">
      <div className="scw-order-card">
        <div className="scw-order-head">
          <span className="scw-order-number">#1042</span>
          <span className="scw-order-status">fulfilled</span>
        </div>

        <div className="scw-order-meta">Ava Chen · Aug 2, 2026</div>

        <div className="scw-order-lines">
          <div className="scw-order-line">
            <img className="scw-order-line-img" src="https://pub-74ac413420c240d4b6830500ed2351c0.r2.dev/uploads/5fd74f04-f442-437c-9ec9-d4832f06b970product-image.png" alt="Minimal Canvas Tote Bag" />

            <span className="scw-order-line-name">Minimal Canvas Tote Bag ×2</span>
            <span className="scw-order-line-price">\$48.00</span>
          </div>

          <div className="scw-order-line">
            <img className="scw-order-line-img" src="https://pub-74ac413420c240d4b6830500ed2351c0.r2.dev/uploads/a3621e28-24ac-4564-84f8-3b7c03d1ce33product-image.png" alt="Soy Wax Candle" />

            <span className="scw-order-line-name">Soy Wax Candle</span>
            <span className="scw-order-line-price">\$22.00</span>
          </div>
        </div>

        <div className="scw-order-total">
          <span>Total</span>
          <span>\$70.00</span>
        </div>

        <div className="scw-order-address">
          <span className="scw-order-address-label">Shipping</span>
          <div>500 Market St</div>
          <div>San Francisco, CA 94105</div>
          <div>United States</div>
        </div>

        <span className="scw-order-link">Track package ↗</span>
      </div>
    </div>

    **Payload**

    <ResponseField name="order" type="object" required>
      Renders nothing without an `order.number`.

      <Expandable title="order">
        <ParamField path="number" type="string" required>
          Order number, shown as `#number`.
        </ParamField>

        <ParamField path="status" type="string">
          Status label, e.g. `fulfilled`.
        </ParamField>

        <ParamField path="created_at" type="string">
          ISO date, shown as the order date.
        </ParamField>

        <ParamField path="customer_name" type="string">
          Customer display name.
        </ParamField>

        <ParamField path="total_amount" type="number">
          Order total in minor units, formatted with `currency`.
        </ParamField>

        <ParamField path="currency" type="string">
          Currency code for the total and line prices.
        </ParamField>

        <ParamField path="items" type="array">
          Line items: `{ name, quantity, price_amount }`.
        </ParamField>

        <ParamField path="shipping_address" type="string">
          Shipping address as a comma-separated string.
        </ParamField>

        <ParamField path="billing_address" type="string">
          Billing address as a comma-separated string.
        </ParamField>

        <ParamField path="tracking_url" type="string">
          Shipment tracking link.
        </ParamField>
      </Expandable>
    </ResponseField>
  </Accordion>

  <Accordion title="Button Link">
    Type: `button_link` — a single link styled as a button.

    **Payload**

    <ResponseField name="label" type="string" required>
      Button text.
    </ResponseField>

    <ResponseField name="url" type="string" required>
      Link target; must be an `http(s)` URL or nothing is rendered.
    </ResponseField>
  </Accordion>
</AccordionGroup>
