diff --git a/src/components/landing/AiLanding.tsx b/src/components/landing/AiLanding.tsx
index ea08d0d5b..daf8bcac4 100644
--- a/src/components/landing/AiLanding.tsx
+++ b/src/components/landing/AiLanding.tsx
@@ -11,6 +11,7 @@ import {
CubeIcon,
DatabaseIcon,
HardDrivesIcon,
+ LayoutIcon,
MicrophoneIcon,
PlugIcon,
RadioIcon,
@@ -21,6 +22,9 @@ import {
} from '@phosphor-icons/react'
import { getLibrary } from '~/libraries'
+import { CodeBlock } from '~/components/markdown/CodeBlock'
+import { usePrefersReducedMotion } from '~/utils/usePrefersReducedMotion'
+import { LandingPromptBox } from './LandingPromptBox'
import {
LandingSection,
LandingSectionIntro,
@@ -28,247 +32,122 @@ import {
LibraryLandingShell,
} from './LibraryLanding'
-const aiPrompt = [
- 'Build me a TanStack Start app using TanStack AI as its driver to showcase AI features, if run inside of an existing app then add a single new page and endpoint to showcase the power of TanStack AI.',
- 'Drive the agent loop with chat(): isomorphic tools via toolDefinition().server() / .client(), add at least 1 tool on the server and on the client, use the headless UI features to build the UI, add a tool that needs approval.',
- 'Reach for the rest of the stack only when the task needs it: Code Mode in an isolate for multi-tool orchestration, a sandboxed coding-agent harness, @tanstack/ai-mcp for MCP servers, memoryMiddleware for cross-session recall, @tanstack/ai-persistence for durable threads and resumable streams.',
- 'Never introduce a hosted gateway, a prescribed UI kit, or a provider-specific wire format. Keep provider capabilities honest: model options, tool support, and modality-specific results stay typed at the adapter boundary, and media or realtime primitives appear only where the selected model supports them.',
-].join(' ')
-
-const providers = [
- {
- name: 'OpenRouter',
- model: 'any of 300+ models',
- capabilities: ['text', 'reasoning', 'tools', 'image'],
- },
- {
- name: 'OpenAI',
- model: 'gpt-5',
- capabilities: ['text', 'reasoning', 'tools', 'image'],
- },
- {
- name: 'Anthropic',
- model: 'claude-sonnet-4',
- capabilities: ['text', 'reasoning', 'tools'],
- },
- {
- name: 'Gemini',
- model: 'gemini-2.5-pro',
- capabilities: ['text', 'reasoning', 'tools', 'media'],
- },
- {
- name: 'Ollama',
- model: 'local model',
- capabilities: ['text', 'tools'],
- },
-]
-
-type AiHeroServer = {
- detail?: string
- dotted?: boolean
- kind?: 'tanstack'
- label: string
-}
-
-type GraphNodePosition = {
- height: number
- label: string
- width: number
- x: number
- y: number
-}
-
-type GraphPoint = {
- x: number
- y: number
-}
-
-const aiHeroClients = [
- 'Vanilla',
- 'React',
- 'Vue',
- 'Solid',
- 'Svelte',
- 'Preact',
- 'Angular',
- 'Octane',
-]
-const aiHeroServers: Array = [
- { label: 'TanStack AI', detail: 'Server', kind: 'tanstack' },
- { label: 'Python', dotted: true },
- { label: 'Go', dotted: true },
- { label: 'PHP', dotted: true },
-]
-const aiHeroProviders = ['OpenRouter', 'OpenAI', 'Anthropic', 'Gemini']
-// ponytail: 8 clients on a fixed 4x2 grid; recompute the columns if the list changes length
-const graphClientNodes = aiHeroClients.map((label, index) => ({
- label,
- x: [10, 112, 214, 316][index % 4] ?? 112,
- y: index < 4 ? 36 : 84,
- width: 94,
- height: 36,
-}))
-const graphAgUiNode: GraphNodePosition & {
- kind: 'tanstack'
-} = {
- label: 'TanStack AI Client',
- kind: 'tanstack',
- x: 142,
- y: 138,
- width: 136,
- height: 58,
-}
-const graphServerNodes = aiHeroServers.map((server, index) => ({
- ...server,
- x: [38, 178, 254, 326][index] ?? 178,
- y: index === 0 ? 254 : 260,
- width: index === 0 ? 124 : 56,
- height: index === 0 ? 54 : 42,
-}))
-const graphProviderNodes = aiHeroProviders.map((label, index) => ({
- label,
- x: 18 + index * 98,
- y: 352,
- width: 78,
- height: 34,
-}))
-const aiHeroMessages = [
- {
- user: 'Build the invoice agent on our stack, not yours.',
- assistant:
- 'Done. Headless client in your app, the agent loop on your server, AG-UI between them. No gateway, no hosted state.',
- },
- {
- user: 'It should ask before it charges a card.',
- assistant:
- 'chargeCard is marked needsApproval, so the run ends as an interrupt. Resolve it and the loop continues from that exact step.',
- },
- {
- user: 'And if we move off this provider?',
- assistant:
- 'Swap the adapter. Your tools, events, and UI never learn the difference.',
- },
-]
+const aiPrompt =
+ 'Install the agent skills from the skills folder of https://github.com/TanStack/ai for my user, read them, then ask me what AI features I want to build, or suggest some.'
// ponytail: the shared --landing-accent-ink is pure black, which reads badly on the
// orange accent fill. Darken the fill instead and use white text on it.
const accentFillClass =
'bg-[linear-gradient(135deg,color-mix(in_srgb,var(--landing-accent)_84%,black),color-mix(in_srgb,var(--landing-accent)_52%,black))] text-white'
-function AdapterDocsLink() {
- const { version } = useParams({ strict: false })
- const library = getLibrary('ai')
-
- return (
-
- See the adapter docs for more.
-
- )
-}
-
-type AiHeroChatMessage = {
- assistant: string
- id: string
- isStreaming: boolean
- user: string
-}
-
export default function AiLanding() {
return (
}
- prompt={aiPrompt}
- promptLabel="Copy AI prompt"
+ headline="AI building blocks for TypeScript. We build the hard parts, you keep your stack."
+ description="TanStack AI is a TypeScript library for building AI features and agents. It ships the agent loop, provider adapters, durability, interrupts, sandboxes, and tools, and plugs into the server, database, and UI you already have."
+ hero={}
+ beforeActions={}
>
-
+ }
- title="Own both sides of the AI interaction."
- body="One route on the server, one hook in the client, and the transport between them is yours. Nothing here is a wrapper around a service we run."
+ eyebrow="Open protocol"
+ icon={}
+ title="AG-UI compliant, in both directions."
+ body="The client sends AG-UI requests and consumes AG-UI events, so the agent on the other end is replaceable: point the same client at a Python, Go, or PHP runtime and it keeps working. Bring your own transport."
+ action={
+
+ AG-UI compliance
+
+ }
/>
-
+
+
+
+
+
+ }
+ title="Typed options for every model."
+ body="Pick a model and TypeScript narrows the fields to what it supports. Input parts for chat. Pixel sizes on one image model and aspect ratio plus resolution on the next. Durations and tiers for video. Resolution for world models. The wrong value fails in the editor, not in production."
+ action={
+
+ Connection adapters
+
+ }
+ />
+
+
+
+
}
- title="Define your agent tools once, re-use them on the server and client."
- body={
- <>
- Our chat() function allows you to define custom
- functions the LLM provider can call (tools) and you define the
- input and output to these functions once and re-use them across
- server and client by providing the specific implementations. Our
- library automatically calls these tools, stops and asks for
- approvals if needed, updates the input to the tools if the user
- changes it after the approval is granted and handles all the
- back and forth between the LLM provider and your app under the
- hood. You only define the tool, we handle the rest.
- >
- }
+ title="Define a tool once. Run it on either side."
+ body="One schema gives you the input and output types on the server and the client. The loop calls the tool, waits for approval when asked, applies the user's edits, and feeds the result back to the model."
+ action={Tools}
/>
-
-
+
}
- title="Swap an LLM provider. Keep the typesafety."
- body={
- <>
- OpenRouter, OpenAI, Anthropic, Gemini, Vertex, Bedrock, Mistral,
- Groq, Grok, Ollama, Cohere, Perplexity, BytePlus, ElevenLabs,
- fal.ai, Lovable, LLM Gateway, and Vercel AI Gateway ship as
- official adapters, and openaiCompatible covers any endpoint that
- speaks the same shape, including a model on your own hardware.{' '}
- Every model from every provider is typesafe.
- When you need to send custom configuration for a specific model,
- send images, files and audio, or native tools like web search,
- every model is type-constrained and if it does not accept those
- options natively you learn about it at compile-time.
- >
- }
+ eyebrow="You own the UI"
+ icon={}
+ title="Messages are parts. Render however you like."
+ body="Text, thinking, tool calls and results all arrive as typed parts with their own state. Loop over the parts and render each one, or hand a component per part type to createChatHook and it picks the right one for you."
+ action={UI integrations}
/>
+
+
+
-
+ }
- title="AG-UI compliant, in both directions."
- body="The client sends AG-UI requests and consumes AG-UI events, with no proprietary stream format and no translation layer in between. That is what makes the agent on the other end replaceable: point the same client at a Python, Go, or PHP AG-UI runtime and it keeps working. The transport is yours too, whether that is SSE, HTTP streams, XHR, RPC, a raw async iterable, or a fetcher you wrote. Nothing to sign up for, no key to hand over, no traffic through us."
+ eyebrow="You own persistence"
+ icon={}
+ title="Your database. Your schema."
+ body="Persistence is two functions: loading and saving a thread. With the ai-persistence skill shipped with the package, your coding agent can wire them to your tables and ORM in one pass."
+ action={Persistence}
/>
-
+
+
+
+
+
+ }
+ title="Refresh mid-answer and nothing is lost."
+ body="Every chunk is written to a log before it is delivered. Drop the socket or refresh the page and the client replays from the last offset instead of losing the model's answer."
+ action={
+
+ Resumable streams
+
+ }
+ />
+
+
}
- title="Sandboxes, code mode, MCP, memory, compaction, skills and more."
- body="We offer more than just a simple chat interface. We allow you to build any AI feature you might need, from automated AI workflows in CI, to web apps consuming LLM providers, chatbots and more."
+ title="Sandboxes, Code Mode, MCP, memory, compaction."
+ body="With each feature as its own package, load what the task needs and leave out the rest."
/>
@@ -279,8 +158,9 @@ export default function AiLanding() {
}
- title="Need to generate images, video, audio and more? We have you covered."
- body="We equally care about every generation, not just text. We offer you a whole suite of utilities to generate images, video, speech, transcription, music and realtime voice with full observability and cost tracking."
+ title="Images, video, speech, voice, and live worlds."
+ body="The same adapters and the same persistence cover every modality, with progress updates and cost tracking built in."
+ action={Generations}
/>
@@ -291,657 +171,573 @@ export default function AiLanding() {
}
- title="Full observability of every action with our devtools"
- body="Our devtools show you every detail about every part of your system, whether you are generating images, video or using chat you can see every action that happened on both the server and the client and easily debug what is going on on both sides."
+ title="See every action on both sides."
+ body="Every tool call, interrupt, memory recall, and finish reason, on the server and in the client, in one timeline."
+ action={Devtools}
/>
+
+
+ }
+ title="Choose what you want to do next."
+ body="Each one is a short guide with copyable code, not a tour."
+ />
+
+
)
}
-function CodeLine({
- children,
- indent = 0,
+const codeWindowClass =
+ 'm-0 min-w-0 rounded-none border-0 [&>div:first-child]:rounded-none [&_pre]:max-h-104 [&_pre]:overflow-auto [&_pre]:rounded-none [&_pre]:text-[11px] [&_pre]:leading-5 sm:[&_pre]:text-xs'
+
+function CodeTabs({
+ preHeightClass,
+ label,
+ samples,
}: {
- children?: React.ReactNode
- indent?: number
+ label: string
+ samples: Array<{ code: string; file: string; name: string }>
+ preHeightClass: string
}) {
- return
-
- A client graph shows eight UI adapters converging on the TanStack AI
- Client over AG-UI, then reaching an agent runtime in TypeScript, Python,
- Go, or PHP, and interchangeable model providers.
-
-
-
+
+
+ A message is a list of parts. A thinking part, then a tool call that
+ moves from awaiting input through approval to complete, then the tool
+ result and the streamed text reply. Below the list, the component
+ registered for the active part.
+
lookupInvoice.client(async ({'{'} id {'}'}) => {'{'}
-
+
{`const url = new URL(\`/invoices/\${id}\`, window.location.origin)`}
-
+
window.history.pushState({'{'} id {'}'}, '', url)
-
+
return {'{'} id, href: url.pathname {'}'}
-
{'})'}
+
{'})'}
>
) : (
- <>
-
- lookupInvoice.server(async ({'{'} id {'}'}) => {'{'}
-
-
- return db.invoices.update({'{'}
-
-
- where: {'{'} id {'}'},
-
-
- data: {'{'} lastViewedAt: new Date(){' '}
- {'}'},
-
-
- {'}'})
-
-
{'})'}
- >
- )}
-
-
- {boundary === 'client'
- ? 'The client implementation uses the typed id to call a browser API. The loop waits for it and feeds the result back to the model.'
- : 'The server implementation uses the same typed id to update a row in your database. The model never sees your credentials.'}
-
- Types narrow to this exact model: its options, its capabilities, its
- input modalities. Pass an image to a text-only model and it fails at
- compile time, not in production.
-
+ <>
+
+ lookupInvoice.server(async ({'{'} id {'}'}) => {'{'}
+
+
+ return db.invoices.update({'{'}
+
+
+ where: {'{'} id {'}'},
+
+
+ data: {'{'} lastViewedAt: new Date(){' '}
+ {'}'},
+
+
+ {'}'})
+
+
{'})'}
+ >
+ )}
+
+ {boundary === 'client'
+ ? 'The client implementation uses the typed id to call a browser API. The loop waits for it and feeds the result back to the model.'
+ : 'The server implementation uses the same typed id to update a row in your database. The model never sees your credentials.'}
+
)
@@ -1105,7 +844,7 @@ function ProtocolMap() {
both directions. The server then talks to a provider such as OpenAI or
Anthropic.
-
+
{nodes.map((node, index) => (
@@ -1131,10 +870,10 @@ function ProtocolCard({
{node.label}
@@ -1146,9 +885,9 @@ function ProtocolCard({
}
return (
-
diff --git a/src/libraries/ai.tsx b/src/libraries/ai.tsx
deleted file mode 100644
index f6da8795c..000000000
--- a/src/libraries/ai.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import { Library } from '.'
-import { PlugIcon, LightningIcon, GearIcon } from '@phosphor-icons/react'
-import { twMerge } from 'tailwind-merge'
-import { ai } from './libraries'
-
-const textStyles = `text-category-data`
-
-export const aiProject = {
- ...ai,
- description: `TanStack AI is a pluggable AI ecosystem that makes it easy for you to build AI features into your apps. Provide tools to LLMs, interrupt chat for user approval, run agents in sandboxes, build headless chat UI, stream from your server to your client, and connect to any AG-UI compatible server or client. Bring your own infrastructure. We offer the pluggable APIs to build on top of.`,
- latestBranch: 'main',
- defaultDocs: 'getting-started/overview',
- featureHighlights: [
- {
- title: 'A Real Agent Loop',
- icon: ,
- description: (
-
- chat() drives the loop and you control every part of it:
- isomorphic tools you place on the client or the server, composable{' '}
- {`(state) => boolean`} stop strategies, and interrupts
- that pause a run for human approval and resume exactly where it
- stopped, with no database required.
-
- ),
- },
- {
- title: 'Bring Your Own Everything',
- icon: ,
- description: (
-
- Your provider, server, transport, auth, and deploy target. Official
- adapters for OpenRouter, OpenAI, Anthropic, Gemini, Vertex, Bedrock,
- Mistral, Groq, Grok, Ollama, Cohere, Perplexity, BytePlus, ElevenLabs,
- fal.ai, Lovable, LLM Gateway, and Vercel AI Gateway, plus{' '}
- openaiCompatible for anything else. Import only what you
- use: every activity is a separate, tree-shakeable module.
-
- A framework-free core with React, Vue, Solid, Svelte, Preact, Angular,
- and React Native bindings on top, plus official Octane bindings from
- the Octane team. All of them speak native AG-UI over SSE, HTTP
- streams, XHR, RPC, or your own transport. No components to fight, no
- styles to override.
-
- ),
- },
- ],
-} satisfies Library
diff --git a/src/libraries/libraries.ts b/src/libraries/libraries.ts
index 3b0f50475..1c7982a3e 100644
--- a/src/libraries/libraries.ts
+++ b/src/libraries/libraries.ts
@@ -691,9 +691,10 @@ export const ai: LibrarySlim = {
...categoryStyles.data,
name: 'TanStack AI',
to: '/ai/latest',
- tagline: 'The headless agent framework for TypeScript. Bring your own stack',
+ tagline:
+ 'AI building blocks for TypeScript. We build the hard parts, you keep the stack.',
description:
- 'TanStack AI is a pluggable AI ecosystem that makes it easy for you to build AI features into your apps. Provide tools to LLMs, interrupt chat for user approval, run agents in sandboxes, build headless chat UI, stream from your server to your client, and connect to any AG-UI compatible server or client. Bring your own infrastructure. We offer the pluggable APIs to build on top of.',
+ 'TanStack AI is a TypeScript library for building AI features and agents. It ships the agent loop, provider adapters, durability, interrupts, sandboxes, and tools, and plugs into the server, database, and UI you already have.',
badge: 'RC',
repo: 'tanstack/ai',
frameworks: [
diff --git a/src/styles/app.css b/src/styles/app.css
index 90b86ca4c..10926a505 100644
--- a/src/styles/app.css
+++ b/src/styles/app.css
@@ -87,6 +87,17 @@ html.theme-switching *::after {
--color-gray-800: #332d24;
--color-gray-900: #201b15;
--color-gray-950: #111111;
+
+ --animate-shimmer: shimmer 2.4s ease-in-out infinite;
+ @keyframes shimmer {
+ 0%,
+ 40% {
+ transform: translateX(-150%);
+ }
+ 100% {
+ transform: translateX(250%);
+ }
+ }
}
/* Keep neutral grays stable before client CSS injection reorders Tailwind layers. */
diff --git a/src/utils/docsNavTabs.ts b/src/utils/docsNavTabs.ts
index 600f549db..0ea4f5e98 100644
--- a/src/utils/docsNavTabs.ts
+++ b/src/utils/docsNavTabs.ts
@@ -6,6 +6,7 @@ export const docsNavTabIds = [
'get-started',
'tutorial',
'guides',
+ 'adapters',
'api',
'examples',
] as const
@@ -17,6 +18,8 @@ export const docsNavTabs: Array<{ id: DocsNavTabId; label: string }> = [
{ id: 'get-started', label: 'Get Started' },
{ id: 'tutorial', label: 'Tutorial' },
{ id: 'guides', label: 'Guides' },
+ // Only shown for libraries whose docs config tags sections with it.
+ { id: 'adapters', label: 'Adapters' },
{ id: 'api', label: 'API' },
{ id: 'examples', label: 'Examples' },
]