The New Default. Your hub for building smart, fast, and sustainable AI software

See now
chatbot interactions wth generative ui

Revolutionizing chatbot interactions with Generative UI

Maciej Korolik
|   Updated Jun 1, 2026

Most AI chatbots today confine users to a text-in, text-out loop. You type a question; you get a paragraph back. That works for simple queries, but it fails the moment the task requires selection, confirmation, visualization, or structured input.

Generative UI addresses this gap by allowing a chatbot to dynamically generate interface components – forms, selectors, charts, summaries – as part of the conversation itself.

Executive Summary

Generative UI moves chatbot interactions beyond text exchanges by letting the LLM decide when to render a custom UI component rather than a text response.

Vercel's AI SDK 3 made this practical: using streamUI from the ai/rsc package, UI components stream from the server via React Server Components – the same way text responses do.

This post walks through a working Proof of Concept built for an imaginary insurance company, where users report car damage and check claim status through a combination of natural conversation and interactive components.

The implementation covers the core submitUserMessage server action, function calling via the tools parameter, and how to capture user input from generated components back into the conversation.

Beyond Text-Based Exchanges

Most AI applications and chatbots today limit user interactions to simple, text-based exchanges. Users input commands or questions, and the chatbot responds with answers. While natural language conversation is convenient, it's often not the right format for the answer.

A claim status is better shown as a list. A car part selection is better handled visually than described in text.

Generative UI allows the interface to create real-time, custom responses based on user input. With Generative UI, a chatbot can present a product comparison, a chart, a table, a form – whatever best serves the context.

Instead of navigating through a fixed set of menus, users engage in natural conversation while the chatbot dynamically generates the UI components that make the most sense for each step.

Simplifying Generative UI Integration in Chatbots: the PoC

Integrating Generative UI into a chatbot involves a core technical challenge: LLM inputs and outputs are text-based, so you need a way to instruct the AI to respond in a form that triggers custom UI components – and your app needs to understand that signal to render correctly.

Vercel's release of AI SDK 3 made this practical. Using this library, adding Generative UI reduces to a single configuration defining available functions and UI components.

The SDK uses React Server Components, streaming UI from the server the same way it streams text – which means fast, performant interactions without the overhead of client-side rendering.

We wanted to test this approach and create a Proof of Concept to showcase the capabilities of Vercel's AI SDK with Generative UI. The vision: a chatbot for an imaginary insurance company that simplifies the complex process of submitting an insurance claim.

Our PoC drew significant inspiration from Vercel's Next.js AI Chatbot demo, an excellent starting point for anyone learning their AI SDK and Generative UI approach.

The chatbot serves two main purposes: reporting car damage and checking the status of submitted claims. These tasks can be completed through natural language conversation. We included four interactive components the LLM can use and display as it deems appropriate for the context.

The Car Selector lets users interact with a custom React component to choose their car from a list associated with their account when reporting a claim, rather than typing it out.

The Parts Selector offers a dynamic diagram for visually selecting and specifying damaged car parts – this standardizes the submitted part names and helps users who don't know the exact terminology.

The Claims List displays a user's current insurance claims and their statuses through an interactive component with real-time data. The Summary Display compiles the provided information into an interactive summary, letting users review, edit, and confirm the details before submitting.

Technical Implementation of the Dynamic Chatbot UI with Vercel's AI SDK

Note on API status: The streamUI helper and ai/rsc package used in this PoC are marked as experimental by Vercel and are not recommended for production use. Vercel's current recommendation is AI SDK UI (useChat and related hooks), which has broader framework support, parallel tool calls, and multi-step tool call support. The code below reflects the original PoC implementation using AI SDK 3 RSC – the architecture and concepts remain instructive, but if you're building new today, refer to the AI SDK RSC migration guide.

The app's behavior is mainly determined by the submitUserMessage() server action. This action takes user input as an argument and returns a result – either streamed text or UI.

It also updates and synchronizes the AI state, which contains the context communicated to the AI model: system messages, function responses, and other relevant information.

The action uses the streamUI helper function from the Vercel AI SDK (specifically in the ai/rsc package) to facilitate UI streaming of answers. This functionality relies on React Server Components, so it's only compatible with frameworks that support this concept, such as Next.js.

Here's a basic example that handles only text messages:

async function submitUserMessage(content: string) {
  'use server'
  const aiState = getMutableAIState<typeof AI>()
  aiState.update({
    ...aiState.get(),
    messages: [
      ...aiState.get().messages,
      {
        id: nanoid(),
        role: 'user',
        content
      }
    ]
  })
  let textStream: undefined | ReturnType<typeof createStreamableValue<string>>
  let textNode: undefined | React.ReactNode
  const result = await streamUI({
    model: openai('gpt-4o-mini'), // Note: original PoC used gpt-3.5-turbo; gpt-4o-mini is the current cost-equivalent replacement
    initial: <SpinnerMessage />,
    system: "[...]", // System prompt where we define the app job and desired behaviour
    messages: [
      ...aiState.get().messages.map((message: any) => ({
        role: message.role,
        content: message.content,
        name: message.name
      }))
    ],
    text: ({ content, done, delta }) => {
      // Text streaming logic:
      if (!textStream) {
        textStream = createStreamableValue('')
        textNode = <BotMessage content={textStream.value} />
      }
      if (done) {
        textStream.done()
        aiState.done({
          ...aiState.get(),
          messages: [
            ...aiState.get().messages,
            {
              id: nanoid(),
              role: 'assistant',
              content
            }
          ]
        })
      } else {
        textStream.update(delta)
      }
      return textNode
    },
  })
  return {
    id: nanoid(),
    display: result.value
  }
}

The function first updates the AI state with the new user message and then uses the streamUI helper to return the answer. This example only handles text messages, but it already streams a React node (<BotMessage content={textStream.value} />) instead of plain text.

Now let's introduce interactivity using the Car Selector as an example. Generative UI relies on function calls, which enable the execution of complex logic – fetching and processing external data before rendering a UI component.

We define functions available to the model in the tools parameter of streamUI. Here's the showCarSelector function definition:

tools: {
    showCarSelector: {
        description:
          'Show user cars and UI for them to select a car. Use it when you want to ask the user to select a car.',
        parameters: z.object({}),
        generate: async function* () {
          yield (
            <BotCard>
              <CarSelectorSkeleton />
            </BotCard>
          )
          const session = await auth()
          const cars = await getUserCars(session?.user?.id)
          const toolCallId = nanoid()
          aiState.done({
            ...aiState.get(),
            messages: [
              ...aiState.get().messages,
              {
                id: nanoid(),
                role: 'assistant',
                content: [
                  {
                    type: 'tool-call',
                    toolName: 'showCarSelector',
                    toolCallId,
                    args: cars
                  }
                ]
              },
              {
                id: nanoid(),
                role: 'tool',
                content: [
                  {
                    type: 'tool-result',
                    toolName: 'showCarSelector',
                    toolCallId,
                    result: cars
                  }
                ]
              }
            ]
          })
          return (
            <BotCard>
              <CarSelector props={cars} />
            </BotCard>
          )
        }
      },
    //  ...other tools definitions
    }

We start by providing a function description to guide the model on when to use it. Then we define the required parameters (none in this case) and create a generate function containing all the logic.

The function is asynchronous – this allows for rendering a skeleton component initially, fetching data from an external source, updating the AI's state with a message history, and finally presenting the UI component.

Once the component is displayed, we capture user input from it by calling submitUserMessage when a user selects a car and describing the action:

{cars.map(car => (
  <Card
    onClick={async () => {
      if (!selectedCar) {
        setSelectedCar(car)
        const response = await submitUserMessage(
          `[User selected car: ${JSON.stringify(car)}]`
        )
        setMessages(currentMessages => [...currentMessages, response])
      }
    }}
    key={car.id}
  >
    {/* Car details */}
  </Card>
))}

Implementing other features follows a similar pattern – each interactive component has a corresponding tool defined in the submitUserMessage() action.

You can view the final result in thisdemo video.

The integration of Generative UI into chatbot applications, powered by the Vercel AI SDK, represents a meaningful advance in interactive app development.

The architecture simplifies the development process and delivers fast, performant interactions through React Server Components – the server handles all logic and rendering, resulting in efficient performance without client-side rendering overhead.

That said, this approach introduces genuine complexity in prompt engineering. Achieving precise rendering of UI components requires careful tuning, and the outcomes can be unpredictable.

The AI performs more reliably when responding to direct user requests – displaying specific data – than when guiding a user through a multi-step interaction like a claim submission. These issues may be resolved by using a model fine-tuned for the specific context.

Generative UI stands ready to offer innovative solutions for user engagement, promising adaptable and fast interfaces.

As the technology evolves and models improve, current limitations around prompt reliability and guidance tasks are likely to shrink – opening up more of the interaction patterns that today require careful engineering.

Why Your Team Should Adopt Generative UI

The technical complexity of this PoC is real, but the business case is straightforward: users who can interact through natural conversation and contextually appropriate UI components complete tasks faster and with fewer errors than users confined to text prompts or fixed form flows.

Reduced friction on complex workflows. Tasks like insurance claims, onboarding sequences, or multi-step configuration processes involve ambiguity, unfamiliar terminology, and sequential decisions. Generative UI handles each step with the right interface – a selector when selection is needed, a summary when confirmation is needed, a diagram when spatial input is needed – rather than forcing every step through text.

Lower development overhead for interactive flows. The tools configuration approach means adding a new interactive component doesn't require rebuilding the conversation flow – you define the component, describe when to use it, and the model decides when to invoke it. This makes iterating on complex flows significantly cheaper than traditional form-based alternatives.

A foundation for more sophisticated AI products. Teams that build with this architecture early develop the muscle for agentic workflows – where AI doesn't just respond to requests but actively guides users through processes. The same patterns used for the Car Selector and Summary Display here apply directly to more complex AI-driven product flows.

At Monterail, these patterns have shaped how we approach AI-native product development.

Cooleaf is one example: rather than building a static dashboard for HR professionals, the team implemented AI that translates engagement data into dynamic, contextual recommendations – a form of generative interface applied to business intelligence rather than chat.

Flink required a different expression of the same principle: operational decisions that previously required engineer involvement were automated through an architecture that generates the right action based on context rather than following fixed rules.

If you're evaluating where Generative UI fits in your product roadmap, or working through how to integrate AI interaction patterns into an existing product, Monterail's AI development services team works through exactly these architecture decisions.

Get in touch to discuss your specific context.

Key Takeaways

  • Generative UI allows chatbots to dynamically render UI components – selectors, forms, charts, summaries – as part of the conversation, rather than confining every interaction to text. This is particularly valuable for multi-step or terminology-heavy workflows.

  • Vercel's AI SDK 3 made this practical through streamUI and React Server Components. The tools parameter in streamUI is where you define which UI components the model can invoke and when. Note: streamUI and the ai/rsc package are experimental – Vercel recommends AI SDK UI (useChat) for new production projects due to better stability, parallel tool calls, and multi-step support.

  • The core implementation pattern: submitUserMessage() server action handles both text responses and UI components; function definitions via tools tell the model when to render a component; user input from generated components feeds back into the conversation via the same submitUserMessage call.

  • Prompt reliability is the main operational challenge. The model is more predictable for display tasks (show me the claims list) than for guidance tasks (walk the user through claim submission). Fine-tuning for the specific context improves reliability.

  • The architecture scales beyond chatbots: the same pattern – AI deciding which interface to render based on context – applies to dashboards, onboarding flows, configuration tools, and any product where the right UI depends on what the user is trying to accomplish.

FAQ

Maciej Korolik
Maciej Korolik
Senior Frontend Developer and AI Expert at Monterail
Linkedin
Maciej is a Senior Frontend Developer and AI Expert at Monterail, specializing in React.js and Next.js. Passionate about AI-driven development, he leads AI initiatives by implementing advanced solutions, educating teams, and helping clients integrate AI technologies into their products. With hands-on experience in generative AI tools, Maciej bridges the gap between innovation and practical application in modern software development.