Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/typed-headless-chat-ui.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@tanstack/ai-client': minor
'@tanstack/ai-react-ui': minor
'@tanstack/ai-solid-ui': minor
'@tanstack/ai-vue-ui': minor
'@tanstack/ai-svelte-ui': minor
---

Add typed headless `createChatUI()` adapters. Chat options control the types of message parts, tools, structured output, and interrupts. `defineComponents` requires a component for every tool name and every registered interrupt id. `InterruptProps` pins a tool approval or a registered generic interrupt. Old Chat orchestration stays importable and deprecated until 1.0.
2 changes: 2 additions & 0 deletions docs/api/ai-client.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ keywords:

Framework-agnostic headless client for managing chat state and streaming.

For typed headless chat UI types and selectors, import `@tanstack/ai-client/ui`. See [Custom Chat UI Adapters](../ui/custom-adapters).

## Installation

```bash
Expand Down
2 changes: 2 additions & 0 deletions docs/api/ai-react.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ keywords:
---

React hooks for TanStack AI, providing convenient React bindings for the headless client.

For a typed headless chat UI, see [React Chat UI](../ui/react) and [Migrate to createChatUI](../migration/create-ui).
For React Native, the documented support surface is narrow: `useChat` with chat
connection adapters. React DOM-specific UI packages and TanStack AI devtools UI
are not part of the React Native support surface.
Expand Down
2 changes: 2 additions & 0 deletions docs/api/ai-solid.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ keywords:

SolidJS primitives for TanStack AI, providing convenient SolidJS bindings for the headless client.

For a typed headless chat UI, see [Solid Chat UI](../ui/solid) and [Migrate to createChatUI](../migration/create-ui).

## Installation

```bash
Expand Down
2 changes: 2 additions & 0 deletions docs/api/ai-svelte.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ keywords:

Svelte 5 bindings for TanStack AI, providing reactive factory functions for the headless client using Svelte runes.

For a typed headless chat UI, see [Svelte Chat UI](../ui/svelte) and [Migrate to createChatUI](../migration/create-ui).

## Installation

```bash
Expand Down
2 changes: 2 additions & 0 deletions docs/api/ai-vue.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ keywords:

Vue composables for TanStack AI, providing convenient Vue 3 bindings for the headless client.

For a typed headless chat UI, see [Vue Chat UI](../ui/vue) and [Migrate to createChatUI](../migration/create-ui).

## Installation

```bash
Expand Down
52 changes: 47 additions & 5 deletions docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,42 @@
}
]
},
{
"label": "UI",
"tab": "guides",
"children": [
{
"label": "React",
"to": "ui/react",
"addedAt": "2026-08-26",
"updatedAt": "2026-08-27"
},
{
"label": "Solid",
"to": "ui/solid",
"addedAt": "2026-08-26",
"updatedAt": "2026-08-27"
},
{
"label": "Vue",
"to": "ui/vue",
"addedAt": "2026-08-26",
"updatedAt": "2026-08-27"
},
{
"label": "Svelte",
"to": "ui/svelte",
"addedAt": "2026-08-26",
"updatedAt": "2026-08-27"
},
{
"label": "Custom Adapters",
"to": "ui/custom-adapters",
"addedAt": "2026-08-26",
"updatedAt": "2026-08-26"
}
]
},
{
"label": "Interrupts",
"tab": "guides",
Expand Down Expand Up @@ -843,6 +879,12 @@
"addedAt": "2026-04-15",
"updatedAt": "2026-07-22"
},
{
"label": "createChatUI",
"to": "migration/create-ui",
"addedAt": "2026-08-26",
"updatedAt": "2026-08-27"
},
{
"label": "From Vercel AI SDK",
"to": "migration/migration-from-vercel-ai",
Expand Down Expand Up @@ -875,19 +917,19 @@
"label": "@tanstack/ai-client",
"to": "api/ai-client",
"addedAt": "2026-04-15",
"updatedAt": "2026-08-24"
"updatedAt": "2026-08-26"
},
{
"label": "@tanstack/ai-react",
"to": "api/ai-react",
"addedAt": "2026-04-15",
"updatedAt": "2026-08-24"
"updatedAt": "2026-08-26"
},
{
"label": "@tanstack/ai-solid",
"to": "api/ai-solid",
"addedAt": "2026-04-15",
"updatedAt": "2026-08-24"
"updatedAt": "2026-08-26"
},
{
"label": "@tanstack/ai-preact",
Expand All @@ -899,13 +941,13 @@
"label": "@tanstack/ai-vue",
"to": "api/ai-vue",
"addedAt": "2026-04-15",
"updatedAt": "2026-08-24"
"updatedAt": "2026-08-26"
},
{
"label": "@tanstack/ai-svelte",
"to": "api/ai-svelte",
"addedAt": "2026-04-15",
"updatedAt": "2026-08-24"
"updatedAt": "2026-08-26"
},
{
"label": "@tanstack/ai-angular",
Expand Down
121 changes: 121 additions & 0 deletions docs/migration/create-ui.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
---
title: Migrate to createChatUI
id: migrate-create-ui
order: 5
description: "Move chat-state ownership out of the old Chat component and onto createChatUI with a typed component map."
keywords:
- tanstack ai
- createChatUI
- migration
- deprecation
---

The old `Chat` component owned chat state and lost configured types. `createChatUI` keeps types from your `chatOptions` and leaves `useChat` in your app.

This is a semantic migration. There is no codemod.

## What changes

1. You call `useChat` or `createChat` yourself.
2. You supply every visible component.
3. Tool inputs stay optional while they stream.
4. Tool approvals come from `chat.interrupts`.
5. Unknown runtime keys can use a fallback or render nothing.
6. `createChatUI()` must run at module scope so identity stays stable.

## Why

The old APIs drop configured types, keep unused properties, use a deprecated approval path, cover only part of the message protocol, and own chat state. Two orchestration models duplicate fixes.

## Minimum versions

- `@tanstack/ai-react-ui` 0.9.0
- `@tanstack/ai-solid-ui` 0.8.0
- `@tanstack/ai-vue-ui` 0.3.0
- `@tanstack/ai-svelte-ui` 0.2.0

Old orchestration exports stay importable until each package's `1.0.0`. `TextPart` and `ThinkingPart` stay supported.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

## Before

```tsx
import { fetchServerSentEvents } from '@tanstack/ai-react'
import { Chat, ChatMessages, ChatInput } from '@tanstack/ai-react-ui'

const connection = fetchServerSentEvents('/api/chat')

export function OldChat() {
return (
<Chat connection={connection}>
<ChatMessages />
<ChatInput />
</Chat>
)
}
```

## After

```tsx
import { fetchServerSentEvents, useChat } from '@tanstack/ai-react'
import { createChatUI } from '@tanstack/ai-react-ui'

const chatOptions = {
connection: fetchServerSentEvents('/api/chat'),
}

const UI = createChatUI(chatOptions)

const components = UI.defineComponents({
layout: ({ renderMessages, renderInput }) => (
<main>
{renderMessages()}
{renderInput()}
</main>
),
message: ({ renderParts }) => <article>{renderParts()}</article>,
input: () => {
const chat = UI.useChat()
return (
<form
onSubmit={(event) => {
event.preventDefault()
const field = event.currentTarget.elements.namedItem('message')
if (!(field instanceof HTMLInputElement)) return
const text = field.value.trim()
if (!text) return
field.value = ''
void chat.sendMessage(text)
}}
>
<input name="message" />
</form>
)
},
parts: { fallback: () => null },
})

export function NewChat() {
const chat = useChat(chatOptions)
return <UI.Chat chat={chat} components={components} />
}
```

## Steps

1. Move `connection`, `tools`, and `interrupts` into a module-level `chatOptions` object.
2. Call `createChatUI(chatOptions)` next to that object.
3. Call `useChat(chatOptions)` in the screen component.
4. Define `layout`, `message`, `parts`, `tools`, and `interrupts` in `defineComponents`.
5. Replace `<Chat>` with `<UI.Chat chat={chat} components={components} />`.

## Gotchas

- A shared `chatOptions` variable does not need `as const`.
- A mapped tool can read `interrupt` and render the approval itself. That approval stays off the list. A component on `interrupts.tools` uses the list.
- Generic interrupts live under `interrupts.generic`: a registered id such as `choosePlan`, plus `fallback`. Unbound interrupts use `fallback`.
- TypeScript requires a `tools` component for every tool name and an `interrupts.generic` component for every interrupt id. `generic.fallback` is optional.
- Matched `tool-result` parts are hidden in automatic traversal. Unmatched results stay visible.
- Nested providers use the nearest chat instance.

See the [React UI guide](../ui/react) for a full map.
34 changes: 34 additions & 0 deletions docs/ui/custom-adapters.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
title: Custom Chat UI Adapters
id: typed-headless-ui-custom-adapters
order: 5
description: "Build a framework adapter on @tanstack/ai-client/ui. The core is types and selectors only."
keywords:
- tanstack ai
- createChatUI
- custom adapter
- headless ui
---

Import `@tanstack/ai-client/ui`. Do not import it from the main client entry.

The subpath gives you:

1. `selectChatUI` to match tool results and split list vs inline interrupts
2. `partTypeToKey` to turn `tool-call` into `toolCall`
3. Option types for tools, generic interrupts, and `outputSchema`

Your adapter owns:

1. Native components and context
2. Native reactivity
3. Render callbacks, slots, or snippets
4. Development warnings for missing mapped keys

Do not add default markup. Do not add a new store. The app owns `useChat` or `createChat`.

Call `selectChatUI({ messages, interrupts, inlineToolNames })`. Automatic traversal skips a `tool-result` only when `matched` is true. Keep unmatched results.

Warn once per missing runtime key in development. Each build tool detects development mode differently, so the adapter prints the warning.

See the [React](./react), [Solid](./solid), [Vue](./vue), and [Svelte](./svelte) adapters for the public names to match: `Chat`, `Provider`, `Messages`, `Message`, `Part`, `Interrupts`, `Interrupt`, and `defineComponents`.
Loading
Loading