If you are yet to setup a project with C1, refer to our Quickstart Guide before getting started with custom components.

Introduction

Custom components provide a flexible and highly customizable way to introduce your own React components for the C1 API to use in its responses. Custom components unlock a lot of tailor-made interfaces that your application might require, and it can now work within C1.
PackageMinimum version
@crayonai/react-ui0.8.31
@thesysai/genui-sdk0.6.34
C1-Versionv-20250915
To illustrate the usefulness of custom components, imagine a flight booking service uses the C1 API to enable generative UI for its users. The user has asked for LOS to JFK flights on September 23, 2025. With the existing C1 components, the information is presented to the user in a C1 Form for selection.
Callout Example
With custom React components, the application can customize how the flight list looks, and have internal states for flight cards and functionality that they want to enable for the user, such as API fetching, interactions, among others.
Callout Example

Integrate Custom Components

The following is a step-by-step guide on how to plug in your React components, along with instructions for C1 to use them in its generations.
For a working example, kindly visit Thesys C1 examples on github
1

Define your React components

Use @thesysai/genui-sdk’s useOnAction to get an onAction callback. This callback is used to handle user interactions with components. In addition to onAction, each component can have its own internal functions for useC1State management, API fetching, etc.Use useC1State to maintain per-response state of the component within C1.Refer to Custom Components Specification below for more details.
src/app/components.tsx
import { useOnAction, useC1State } from "@thesysai/genui-sdk";

export const FlightList = ({ flights }: { flights: Flight[] }) => {
  const onAction = useOnAction();

  // State management to manage component's internal state
  const { getValue, setValue } = useC1State("FlightList")

  return (
    <div>
      <h3>Available Flights</h3>
      ...
      {flights.map((flight) => (
        <FlightCard
          // onSelect trigger for the FlightCard
          onSelect={() =>
            onAction(
              "Select Flight", // Human-friendly message
              `User selected flight ${flight.flightNumber}` // LLM-specific message with user context
            )
          }
          ...
        />
      ))}
    </div>
  );
};
2

Define the component prop schema

In order to inform C1 about the expected props for your component, start by defining a Zod-based schema.
src/app/api/chat/route.ts
import { z } from "zod";

// Schema of a flight
const flightSchema = z
  .object({
    flightNumber: z.string(),
    departure: z.string(),
    arrival: z.string(),
    layover: z.string().optional(),
    ...
  })
  .describe(
    "Represents a single flight option including schedule, price, and stops."
  );

// Schema of the flight list component props
const FlightListSchema = z
  .object({
    flights: z.array(flightSchema),
  })
  // Always include descriptive text for the LLM to understand the component
  .describe(
    "Displays a list of available flights. Renders rich cards with airline, route, times, stops, and price. Includes a clear 'Select' action for each item."
  );
3

Pass the schema to the C1 API

Using the schema described above, convert them as JSON schemas and send it as a part of your C1 API payload.
Since we used the name FlightList for the React component in the frontend, the CUSTOM_COMPONENT_SCHEMAS must have the same key, FlightList.
src/app/api/chat/route.ts
// Schema to be passed to the C1 API
const CUSTOM_COMPONENT_SCHEMAS = {
  FlightList: zodToJsonSchema(FlightListSchema),
  ... // other custom components
};

export async function POST(req: NextRequest) {
  ...
  const client = new OpenAI({
    baseURL: "https://api.thesys.dev/v1/embed/",
    apiKey: process.env.THESYS_API_KEY,
  });

  const llmStream = client.chat.completions.runTools({
    ...
    model: "c1/anthropic/claude-sonnet-4/v-20250915",
    // Pass custom component schema
    metadata: {
      thesys: JSON.stringify({
        c1_custom_components: CUSTOM_COMPONENT_SCHEMAS,
      }),
    },
  });

  ...
}
4

Pass the React components to GenUI SDK for rendering

Custom React components can be used by the <C1Chat> and <C1Component> components.

C1Chat

src/app/page.tsx
import { C1Chat } from "@thesysai/genui-sdk";
import { FlightList } from "./components";

export default function Home() {
  return (
    <C1Chat
      apiUrl="/api/chat"
      customizeC1={{
        customComponents: { FlightList },
      }}
    />
  );
}

C1Component

...
import { C1Component } from "@thesysai/genui-sdk";
import { FlightList } from "./components";

<C1Component
  customComponents={{ FlightList }}
  ...
/>;

Custom Component Specification

useOnAction

Returns a callback to record a user action with both a user-facing label and an LLM-oriented description. Returns
callback
(humanFriendlyMessage: string, llmFriendlyMessage: string) => void
Callback to dispatch the action.
Callback Arguments
humanFriendlyMessage
string
required
Visible to the user; concise, human-readable label for the action. Used to give feedback to the user about their action.Eg: When submitting a form for a trip planner, the human-friendly message can be “Submit response”.
llmFriendlyMessage
string
required
Sent to the LLM; richer context describing what happened. Used to send the contents of the user action to the LLM.Eg: When submitting a form for a trip planner, the LLM-friendly message can be “User selected ${form.destination}, from ${form.startDate} to ${form.endDate}”.
Refer to Interactivity for more details on onAction, and how human-friendly and llm-friendly messages work.

useC1State

Access a named piece of component state with getter and setter helpers. Arguments
name
string
required
The state field key you wish to read/write.
Returns
getValue
() => any
Function that returns the current state for the given name.
setValue
(value: any) => void
Updates the field and triggers any save/persist callbacks.